From 86ce10cf958df118c538c9f54211de3da563cdde Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Thu, 4 Apr 2024 16:26:22 +0400 Subject: [PATCH] ajout t et mapper --- nur_sbin/update_classes.php | 60 ++- nur_src/io/csv/CsvReader.php | 2 + nur_src/io/csv/CsvWriter.php | 2 + nur_src/mapper/app/DatareaderApp.php | 33 ++ nur_src/mapper/app/datareader_command.php | 259 +++++++++++ nur_src/mapper/base/CapacitorConsumer.php | 58 +++ nur_src/mapper/base/Consumer.php | 222 ++++++++++ nur_src/mapper/base/FuncMapper.php | 90 ++++ nur_src/mapper/base/FuncMapper_command.php | 58 +++ nur_src/mapper/base/Mapper.php | 362 ++++++++++++++++ nur_src/mapper/base/MapperAggregate.php | 71 +++ nur_src/mapper/base/Producer.php | 88 ++++ nur_src/mapper/base/ProducerAggregate.php | 78 ++++ nur_src/mapper/base/PushProducer.php | 33 ++ nur_src/mapper/base/StopException.php | 14 + nur_src/mapper/base/Tencoding.php | 30 ++ nur_src/mapper/base/Tparams_command.php | 24 + nur_src/mapper/base/Ttmpwriter.php | 74 ++++ nur_src/mapper/base/capacitor/Capacitor.php | 145 +++++++ nur_src/mapper/base/capacitor/ICapacitor.php | 62 +++ .../mapper/base/capacitor/SqliteCapacitor.php | 30 ++ nur_src/mapper/base/capacitor/TCapacitor.php | 17 + nur_src/mapper/base/encoding_utils.php | 9 + nur_src/mapper/base/mapper_utils.php | 91 ++++ nur_src/mapper/base/mappers_command.php | 72 +++ nur_src/mapper/base/mark_utils.php | 48 ++ nur_src/mapper/base/oobd/IOobdManager.php | 72 +++ nur_src/mapper/base/oobd/OobdManager.php | 20 + nur_src/mapper/base/oobd/TOobdManager.php | 119 +++++ nur_src/mapper/base/producer_utils.php | 23 + nur_src/mapper/csv/AbstractCsvMapper.php | 84 ++++ nur_src/mapper/csv/Assoc2CsvMapper.php | 153 +++++++ .../mapper/csv/Assoc2CsvMapper_command.php | 33 ++ nur_src/mapper/csv/Csv2AssocMapper.php | 145 +++++++ .../mapper/csv/Csv2AssocMapper_command.php | 33 ++ nur_src/mapper/csv/CsvReader.php | 212 +++++++++ nur_src/mapper/csv/CsvReader_command.php | 69 +++ nur_src/mapper/csv/CsvWriter.php | 195 +++++++++ nur_src/mapper/csv/CsvWriter_command.php | 62 +++ nur_src/mapper/csv/csv_defaults.php | 52 +++ nur_src/mapper/fsv/Assoc2FsvMapper.php | 118 +++++ .../mapper/fsv/Assoc2FsvMapper_command.php | 51 +++ nur_src/mapper/fsv/Fsv2AssocMapper.php | 164 +++++++ .../mapper/fsv/Fsv2AssocMapper_command.php | 51 +++ nur_src/mapper/fsv/FsvReader.php | 174 ++++++++ nur_src/mapper/fsv/FsvReader_command.php | 80 ++++ nur_src/mapper/fsv/FsvSchema.php | 385 ++++++++++++++++ nur_src/mapper/fsv/FsvWriter.php | 114 +++++ nur_src/mapper/fsv/FsvWriter_command.php | 73 ++++ nur_src/mapper/fsv/fsv_defaults.php | 7 + nur_src/mapper/item/AbstractStringMapper.php | 410 ++++++++++++++++++ nur_src/mapper/item/Assoc2SeqMapper.php | 147 +++++++ .../mapper/item/Assoc2SeqMapper_command.php | 33 ++ nur_src/mapper/item/AttributeFilterMapper.php | 112 +++++ .../item/AttributeFilterMapper_command.php | 79 ++++ nur_src/mapper/item/EnsureLatin1Mapper.php | 27 ++ nur_src/mapper/item/GenericMapper_command.php | 40 ++ nur_src/mapper/item/ItemFilterMapper.php | 400 +++++++++++++++++ .../mapper/item/ItemFilterMapper_command.php | 75 ++++ nur_src/mapper/item/LoggerConsumer.php | 36 ++ nur_src/mapper/item/NumberMapper.php | 10 + nur_src/mapper/item/NumberMapper_command.php | 33 ++ nur_src/mapper/item/SchemaMapper.php | 260 +++++++++++ nur_src/mapper/item/SchemaMapper_command.php | 51 +++ nur_src/mapper/item/Seq2AssocMapper.php | 109 +++++ .../mapper/item/Seq2AssocMapper_command.php | 49 +++ nur_src/mapper/item/StreamMapper.php | 145 +++++++ nur_src/mapper/item/StreamMapper_command.php | 47 ++ nur_src/mapper/item/StringMapper.php | 143 ++++++ nur_src/mapper/item/StringMapper_command.php | 51 +++ nur_src/mapper/item/TextMapper.php | 144 ++++++ nur_src/mapper/item/TextMapper_command.php | 51 +++ nur_src/mapper/json/JsonReader.php | 112 +++++ nur_src/mapper/json/JsonReader_command.php | 45 ++ nur_src/mapper/json/JsonWriter.php | 91 ++++ nur_src/mapper/json/JsonWriter_command.php | 58 +++ nur_src/mapper/json/YamlReader.php | 91 ++++ nur_src/mapper/json/YamlReader_command.php | 45 ++ nur_src/mapper/json/YamlWriter.php | 84 ++++ nur_src/mapper/json/YamlWriter_command.php | 45 ++ nur_src/mapper/line/IconvMapper.php | 107 +++++ nur_src/mapper/line/IconvMapper_command.php | 47 ++ nur_src/mapper/line/LineReader.php | 93 ++++ nur_src/mapper/line/LineReader_command.php | 55 +++ nur_src/mapper/line/LineWriter.php | 67 +++ nur_src/mapper/line/LineWriter_command.php | 55 +++ nur_src/mapper/pl.php | 17 + nur_src/t/TestCase.php | 53 +++ nur_tbin/datareader.php | 7 + nur_tbin/samples/csv_data.csv | 3 + nur_tbin/samples/fsv_data.fsv | 2 + nur_tbin/samples/fsv_schema.php | 6 + nur_tbin/samples/json_data.json | 2 + nur_tbin/samples/line_data.txt | 9 + nur_tbin/samples/yaml_data.yml | 8 + nur_tests/mapper/base/ConsumerTest.php | 135 ++++++ nur_tests/mapper/base/MapperAggregateTest.php | 20 + nur_tests/mapper/base/MapperTest.php | 183 ++++++++ nur_tests/mapper/base/ProducerTest.php | 31 ++ .../mapper/base/capacitor/CapacitorTest.php | 107 +++++ nur_tests/mapper/base/impl/Add2Mapper.php | 20 + nur_tests/mapper/base/impl/AddMapper.php | 17 + .../mapper/base/impl/IdentMapToMapper.php | 10 + .../mapper/base/impl/IdentReturnMapper.php | 10 + nur_tests/mapper/base/impl/MultMapper.php | 10 + nur_tests/mapper/base/impl/NumberProducer.php | 10 + nur_tests/mapper/base/impl/PlusOneMapper.php | 10 + nur_tests/mapper/base/impl/ReduceMapper.php | 20 + nur_tests/mapper/base/impl/ResultConsumer.php | 16 + nur_tests/mapper/base/impl/SofEofMapper.php | 12 + nur_tests/mapper/base/impl/TwiceMapper.php | 10 + nur_tests/mapper/csv/Assoc2CsvMapperTest.php | 58 +++ nur_tests/mapper/csv/Csv2AssocMapperTest.php | 114 +++++ nur_tests/mapper/csv/CsvReaderTest.php | 86 ++++ nur_tests/mapper/csv/CsvWriterTest.php | 55 +++ nur_tests/mapper/csv/cp1252.csv | 2 + nur_tests/mapper/csv/utf8.csv | 2 + nur_tests/mapper/fsv/Fsv2AssocMapperTest.php | 48 ++ nur_tests/mapper/fsv/FsvReaderTest.php | 87 ++++ nur_tests/mapper/fsv/FsvSchemaTest.php | 58 +++ nur_tests/mapper/fsv/latin1.fsv | 2 + nur_tests/mapper/item/AssocMapperTest.php | 52 +++ .../mapper/item/AttributeFilterMapperTest.php | 39 ++ .../mapper/item/ItemFilterMapperTest.php | 24 + sync-nur.sh | 7 + 125 files changed, 9314 insertions(+), 16 deletions(-) create mode 100644 nur_src/mapper/app/DatareaderApp.php create mode 100644 nur_src/mapper/app/datareader_command.php create mode 100644 nur_src/mapper/base/CapacitorConsumer.php create mode 100644 nur_src/mapper/base/Consumer.php create mode 100644 nur_src/mapper/base/FuncMapper.php create mode 100644 nur_src/mapper/base/FuncMapper_command.php create mode 100644 nur_src/mapper/base/Mapper.php create mode 100644 nur_src/mapper/base/MapperAggregate.php create mode 100644 nur_src/mapper/base/Producer.php create mode 100644 nur_src/mapper/base/ProducerAggregate.php create mode 100644 nur_src/mapper/base/PushProducer.php create mode 100644 nur_src/mapper/base/StopException.php create mode 100644 nur_src/mapper/base/Tencoding.php create mode 100644 nur_src/mapper/base/Tparams_command.php create mode 100644 nur_src/mapper/base/Ttmpwriter.php create mode 100644 nur_src/mapper/base/capacitor/Capacitor.php create mode 100644 nur_src/mapper/base/capacitor/ICapacitor.php create mode 100644 nur_src/mapper/base/capacitor/SqliteCapacitor.php create mode 100644 nur_src/mapper/base/capacitor/TCapacitor.php create mode 100644 nur_src/mapper/base/encoding_utils.php create mode 100644 nur_src/mapper/base/mapper_utils.php create mode 100644 nur_src/mapper/base/mappers_command.php create mode 100644 nur_src/mapper/base/mark_utils.php create mode 100644 nur_src/mapper/base/oobd/IOobdManager.php create mode 100644 nur_src/mapper/base/oobd/OobdManager.php create mode 100644 nur_src/mapper/base/oobd/TOobdManager.php create mode 100644 nur_src/mapper/base/producer_utils.php create mode 100644 nur_src/mapper/csv/AbstractCsvMapper.php create mode 100644 nur_src/mapper/csv/Assoc2CsvMapper.php create mode 100644 nur_src/mapper/csv/Assoc2CsvMapper_command.php create mode 100644 nur_src/mapper/csv/Csv2AssocMapper.php create mode 100644 nur_src/mapper/csv/Csv2AssocMapper_command.php create mode 100644 nur_src/mapper/csv/CsvReader.php create mode 100644 nur_src/mapper/csv/CsvReader_command.php create mode 100644 nur_src/mapper/csv/CsvWriter.php create mode 100644 nur_src/mapper/csv/CsvWriter_command.php create mode 100644 nur_src/mapper/csv/csv_defaults.php create mode 100644 nur_src/mapper/fsv/Assoc2FsvMapper.php create mode 100644 nur_src/mapper/fsv/Assoc2FsvMapper_command.php create mode 100644 nur_src/mapper/fsv/Fsv2AssocMapper.php create mode 100644 nur_src/mapper/fsv/Fsv2AssocMapper_command.php create mode 100644 nur_src/mapper/fsv/FsvReader.php create mode 100644 nur_src/mapper/fsv/FsvReader_command.php create mode 100644 nur_src/mapper/fsv/FsvSchema.php create mode 100644 nur_src/mapper/fsv/FsvWriter.php create mode 100644 nur_src/mapper/fsv/FsvWriter_command.php create mode 100644 nur_src/mapper/fsv/fsv_defaults.php create mode 100644 nur_src/mapper/item/AbstractStringMapper.php create mode 100644 nur_src/mapper/item/Assoc2SeqMapper.php create mode 100644 nur_src/mapper/item/Assoc2SeqMapper_command.php create mode 100644 nur_src/mapper/item/AttributeFilterMapper.php create mode 100644 nur_src/mapper/item/AttributeFilterMapper_command.php create mode 100644 nur_src/mapper/item/EnsureLatin1Mapper.php create mode 100644 nur_src/mapper/item/GenericMapper_command.php create mode 100644 nur_src/mapper/item/ItemFilterMapper.php create mode 100644 nur_src/mapper/item/ItemFilterMapper_command.php create mode 100644 nur_src/mapper/item/LoggerConsumer.php create mode 100644 nur_src/mapper/item/NumberMapper.php create mode 100644 nur_src/mapper/item/NumberMapper_command.php create mode 100644 nur_src/mapper/item/SchemaMapper.php create mode 100644 nur_src/mapper/item/SchemaMapper_command.php create mode 100644 nur_src/mapper/item/Seq2AssocMapper.php create mode 100644 nur_src/mapper/item/Seq2AssocMapper_command.php create mode 100644 nur_src/mapper/item/StreamMapper.php create mode 100644 nur_src/mapper/item/StreamMapper_command.php create mode 100644 nur_src/mapper/item/StringMapper.php create mode 100644 nur_src/mapper/item/StringMapper_command.php create mode 100644 nur_src/mapper/item/TextMapper.php create mode 100644 nur_src/mapper/item/TextMapper_command.php create mode 100644 nur_src/mapper/json/JsonReader.php create mode 100644 nur_src/mapper/json/JsonReader_command.php create mode 100644 nur_src/mapper/json/JsonWriter.php create mode 100644 nur_src/mapper/json/JsonWriter_command.php create mode 100644 nur_src/mapper/json/YamlReader.php create mode 100644 nur_src/mapper/json/YamlReader_command.php create mode 100644 nur_src/mapper/json/YamlWriter.php create mode 100644 nur_src/mapper/json/YamlWriter_command.php create mode 100644 nur_src/mapper/line/IconvMapper.php create mode 100644 nur_src/mapper/line/IconvMapper_command.php create mode 100644 nur_src/mapper/line/LineReader.php create mode 100644 nur_src/mapper/line/LineReader_command.php create mode 100644 nur_src/mapper/line/LineWriter.php create mode 100644 nur_src/mapper/line/LineWriter_command.php create mode 100644 nur_src/mapper/pl.php create mode 100644 nur_src/t/TestCase.php create mode 100755 nur_tbin/datareader.php create mode 100644 nur_tbin/samples/csv_data.csv create mode 100644 nur_tbin/samples/fsv_data.fsv create mode 100644 nur_tbin/samples/fsv_schema.php create mode 100644 nur_tbin/samples/json_data.json create mode 100644 nur_tbin/samples/line_data.txt create mode 100644 nur_tbin/samples/yaml_data.yml create mode 100644 nur_tests/mapper/base/ConsumerTest.php create mode 100644 nur_tests/mapper/base/MapperAggregateTest.php create mode 100644 nur_tests/mapper/base/MapperTest.php create mode 100644 nur_tests/mapper/base/ProducerTest.php create mode 100644 nur_tests/mapper/base/capacitor/CapacitorTest.php create mode 100644 nur_tests/mapper/base/impl/Add2Mapper.php create mode 100644 nur_tests/mapper/base/impl/AddMapper.php create mode 100644 nur_tests/mapper/base/impl/IdentMapToMapper.php create mode 100644 nur_tests/mapper/base/impl/IdentReturnMapper.php create mode 100644 nur_tests/mapper/base/impl/MultMapper.php create mode 100644 nur_tests/mapper/base/impl/NumberProducer.php create mode 100644 nur_tests/mapper/base/impl/PlusOneMapper.php create mode 100644 nur_tests/mapper/base/impl/ReduceMapper.php create mode 100644 nur_tests/mapper/base/impl/ResultConsumer.php create mode 100644 nur_tests/mapper/base/impl/SofEofMapper.php create mode 100644 nur_tests/mapper/base/impl/TwiceMapper.php create mode 100644 nur_tests/mapper/csv/Assoc2CsvMapperTest.php create mode 100644 nur_tests/mapper/csv/Csv2AssocMapperTest.php create mode 100644 nur_tests/mapper/csv/CsvReaderTest.php create mode 100644 nur_tests/mapper/csv/CsvWriterTest.php create mode 100644 nur_tests/mapper/csv/cp1252.csv create mode 100644 nur_tests/mapper/csv/utf8.csv create mode 100644 nur_tests/mapper/fsv/Fsv2AssocMapperTest.php create mode 100644 nur_tests/mapper/fsv/FsvReaderTest.php create mode 100644 nur_tests/mapper/fsv/FsvSchemaTest.php create mode 100644 nur_tests/mapper/fsv/latin1.fsv create mode 100644 nur_tests/mapper/item/AssocMapperTest.php create mode 100644 nur_tests/mapper/item/AttributeFilterMapperTest.php create mode 100644 nur_tests/mapper/item/ItemFilterMapperTest.php diff --git a/nur_sbin/update_classes.php b/nur_sbin/update_classes.php index bb56217..ea87ed0 100755 --- a/nur_sbin/update_classes.php +++ b/nur_sbin/update_classes.php @@ -2,15 +2,7 @@ "nur\\io\\", "path" => __DIR__."/../nur_src/io", "classes" => [ - LineReader::class, - CsvReader::class, - CsvWriter::class, - FsvReader::class, - FsvWriter::class, - JsonReader::class, - YamlReader::class, + nur\io\line\LineReader::class, + nur\io\csv\CsvReader::class, + nur\io\csv\CsvWriter::class, + nur\io\fsv\FsvReader::class, + nur\io\fsv\FsvWriter::class, + nur\io\json\JsonReader::class, + nur\io\json\YamlReader::class, ], ], "v-bs3" => [ "package" => "nur\\v\\bs3\\", "path" => __DIR__."/../nur_src/v/bs3", "classes" => [ - Bs3IconManager::class, + nur\v\bs3\Bs3IconManager::class, + ], + ], + "mapper" => [ + "package" => "nur\\mapper\\", + "path" => __DIR__."/../nur_src/mapper", + "classes" => [ + # base + nur\mapper\base\Mapper::class, + nur\mapper\base\FuncMapper::class, + # line + nur\mapper\line\IconvMapper::class, + # item + nur\mapper\item\StringMapper::class, + nur\mapper\item\TextMapper::class, + #nur\mapper\item\NumberMapper::class, + nur\mapper\item\StreamMapper::class, + nur\mapper\item\Seq2AssocMapper::class, + nur\mapper\item\Assoc2SeqMapper::class, + nur\mapper\item\SchemaMapper::class, + nur\mapper\item\ItemFilterMapper::class, + nur\mapper\item\AttributeFilterMapper::class, + # csv + nur\mapper\csv\Csv2AssocMapper::class, + nur\mapper\csv\Assoc2CsvMapper::class, + nur\mapper\csv\CsvReader::class, # après Csv2AssocMapper + nur\mapper\csv\CsvWriter::class, # après Assoc2CsvMapper + # fsv + nur\mapper\fsv\Fsv2AssocMapper::class, + nur\mapper\fsv\Assoc2FsvMapper::class, + nur\mapper\fsv\FsvReader::class, # après Fsv2AssocMapper + nur\mapper\fsv\FsvWriter::class, # après Assoc2FsvMapper + # json + nur\mapper\json\JsonReader::class, + nur\mapper\json\JsonWriter::class, + nur\mapper\json\YamlReader::class, + nur\mapper\json\YamlWriter::class, ], ], ]; diff --git a/nur_src/io/csv/CsvReader.php b/nur_src/io/csv/CsvReader.php index c717762..2e448fc 100644 --- a/nur_src/io/csv/CsvReader.php +++ b/nur_src/io/csv/CsvReader.php @@ -29,6 +29,7 @@ use nur\ref\ref_csv; * @method int setSkipLines(int $value) * @method bool|null setParseHeaders(?bool $value) * @method array|null setHeaders(?array $value) + * @method setHeaderMappings($value) * @method string|null setMapEmpty(?string $value) */ class CsvReader extends Parametrable implements IteratorAggregate, ICloseable { @@ -187,6 +188,7 @@ class CsvReader extends Parametrable implements IteratorAggregate, ICloseable { 'setSkipLines' => 'skip_lines', 'setParseHeaders' => 'parse_headers', 'setHeaders' => 'headers', + 'setHeaderMappings' => 'header_mappings', 'setMapEmpty' => 'map_empty', ]; #--autogen-dynamic-- diff --git a/nur_src/io/csv/CsvWriter.php b/nur_src/io/csv/CsvWriter.php index d148544..4e0801d 100644 --- a/nur_src/io/csv/CsvWriter.php +++ b/nur_src/io/csv/CsvWriter.php @@ -24,6 +24,7 @@ use nur\ref\ref_csv; * @method bool setMultiSchema(bool $value) * @method array|null setHeaders(?array $value) * @method bool setOutputHeaders(bool $value) + * @method setHeaderMappings($value) */ class CsvWriter extends Parametrable { use Tparametrable, Ttmpwriter, Tencoding, Tfilter; @@ -186,6 +187,7 @@ class CsvWriter extends Parametrable { 'setMultiSchema' => 'multi_schema', 'setHeaders' => 'headers', 'setOutputHeaders' => 'output_headers', + 'setHeaderMappings' => 'header_mappings', ]; #--autogen-dynamic-- } diff --git a/nur_src/mapper/app/DatareaderApp.php b/nur_src/mapper/app/DatareaderApp.php new file mode 100644 index 0000000..4710eb9 --- /dev/null +++ b/nur_src/mapper/app/DatareaderApp.php @@ -0,0 +1,33 @@ + "Gérer un flux csv", + "usage" => "[-f input] [-o output]", + + "dynamic_command" => datareader_command::class, + "autoremains" => false, + + "sections" => [ + Application::VERBOSITY_SECTION, + ], + datareader_command::IF_OPTION, + datareader_command::IFO_OPTION, + datareader_command::IE_OPTION, + datareader_command::OF_OPTION, + datareader_command::OFO_OPTION, + datareader_command::OE_OPTION, + ["args" => [["value"]], "action" => [datareader_command::class, "set_input_file"]], + ]; + + function main() { + [$producer, $consumer] = datareader_command::get(); + + $consumer->setUseTmpfile($producer->getInput()); + $consumer->consume($producer, ...mappers_command::$mappers); + } +} diff --git a/nur_src/mapper/app/datareader_command.php b/nur_src/mapper/app/datareader_command.php new file mode 100644 index 0000000..d13a682 --- /dev/null +++ b/nur_src/mapper/app/datareader_command.php @@ -0,0 +1,259 @@ + [ + "exts" => [".csv"], + "formats" => ["csv", "c"], + "reader" => [CsvReader_command::class, CsvReader::class], + "writer" => [CsvWriter_command::class, CsvWriter::class], + "set_input_encoding" => "setEncodingFilter", + "set_output_encoding" => "setEncodingFilter", + ], + "fsv" => [ + "exts" => [".fsv"], + "formats" => ["fsv", "v"], + "reader" => [FsvReader_command::class, FsvReader::class], + "writer" => [FsvWriter_command::class, FsvWriter::class], + "set_input_encoding" => "setInputEncoding", + "set_output_encoding" => "setOutputEncoding", + ], + "json" => [ + "exts" => [".json"], + "formats" => ["json", "j"], + "reader" => [JsonReader_command::class, JsonReader::class], + "writer" => [JsonWriter_command::class, JsonWriter::class], + "set_input_encoding" => false, + "set_output_encoding" => false, + ], + "yaml" => [ + "exts" => [".yaml", ".yml"], + "formats" => ["yaml", "l"], + "reader" => [YamlReader_command::class, YamlReader::class], + "writer" => [YamlWriter_command::class, YamlWriter::class], + "set_input_encoding" => false, + "set_output_encoding" => false, + ], + "line" => [ + "exts" => [], + "formats" => ["line"], + "reader" => [LineReader_command::class, LineReader::class], + "writer" => [LineWriter_command::class, LineWriter::class], + "set_input_encoding" => "setEncodingFilter", + "set_output_encoding" => "setEncodingFilter", + ], + ]; + + const IF_OPTION = ["-f", "--input", "args" => "file", + "action" => [self::class, "set_input_file"], + ]; + const IFO_OPTION = ["-F", "--input-format", "args" => "value", + "action" => [self::class, "set_input_format"], + ]; + const IE_OPTION = ["-e", "--input-encoding", "args" => "value", + "action" => [self::class, "set_input_encoding"], + ]; + const OF_OPTION = ["-o", "--output", "args" => "file", + "action" => [self::class, "set_output_file"], + ]; + const OFO_OPTION = ["-O", "--output-format", "args" => "value", + "action" => [self::class, "set_output_format"], + ]; + const OE_OPTION = ["-t", "--output-encoding", "args" => "value", + "action" => [self::class, "set_output_encoding"], + ]; + + private static $input_file; + + static function set_input_file($file): void { + self::$input_file = A::last(A::with($file)); + } + + private static $input_format; + + static function set_input_format(string $input_format) { + self::$input_format = $input_format; + } + + private static $input_encoding; + + static function set_input_encoding(string $input_encoding) { + self::$input_encoding = $input_encoding; + } + + private static $output_file; + + static function set_output_file($file): void { + self::$output_file = A::last(A::with($file)); + } + + private static $output_format; + + static function set_output_format(string $output_format) { + self::$output_format = $output_format; + } + + private static $output_encoding; + + static function set_output_encoding(string $output_encoding) { + self::$output_encoding = $output_encoding; + } + + static function get(): array { + $producer = null; + $input_file = self::$input_file; + $input_format = self::$input_format; + if ($input_file !== null || $input_format !== null) { + if ($input_file !== null && $input_format === null) { + foreach (self::DEFS as $def) { + foreach (A::with($def["exts"]) as $ext) { + if (str::_ends_with($ext, $input_file)) { + $input_format = A::first(A::with($def["formats"])); + break 2; + } + } + } + if ($input_format === null) $input_format = "line"; + } + $found = false; + foreach (self::DEFS as $def) { + foreach (A::with($def["formats"]) as $format) { + if ($input_format == $format) { + $found = true; + $producerClass = $def["reader"][1]; + $producer = new $producerClass($input_file); + $input_encoding = self::$input_encoding; + if ($input_encoding !== null) { + $setInputEncoding = $def["set_input_encoding"]; + if ($setInputEncoding) { + $producer->$setInputEncoding($input_encoding); + } + } + break 2; + } + } + } + if (!$found) { + throw new ValueException("$input_format: format invalide"); + } + } + if ($producer === null) { + foreach (self::DEFS as $def) { + $commandClass = $def["reader"][0]; + $producer = $commandClass::get(false); + if ($producer !== null) break; + } + } + + $consumer = null; + $output_file = self::$output_file; + $output_format = self::$output_format; + if ($output_file !== null || $output_format !== null) { + if ($output_file !== null && $output_format === null) { + foreach (self::DEFS as $def) { + foreach (A::with($def["exts"]) as $ext) { + if (str::_ends_with($ext, $output_file)) { + $output_format = A::first(A::with($def["formats"])); + break 2; + } + } + } + if ($output_format === null) $output_format = "line"; + } + $found = false; + foreach (self::DEFS as $def) { + foreach (A::with($def["formats"]) as $format) { + if ($output_format == $format) { + $found = true; + $consumerClass = $def["writer"][1]; + $consumer = new $consumerClass($output_file); + $output_encoding = self::$output_encoding; + if ($output_encoding !== null) { + $setOutputEncoding = $def["set_output_encoding"]; + if ($setOutputEncoding) { + $consumer->$setOutputEncoding($output_encoding); + } + } + break 2; + } + } + } + if (!$found) { + throw new ValueException("$output_format: format invalide"); + } + } + if ($consumer === null) { + foreach (self::DEFS as $def) { + $commandClass = $def["writer"][0]; + $consumer = $commandClass::get(false); + if ($consumer !== null) break; + } + } + if ($consumer === null) { + foreach (self::DEFS as $def) { + $producerClass = $def["reader"][1]; + $commandClass = $def["writer"][0]; + if ($producer instanceof $producerClass) { + $consumer = $commandClass::get(); + break; + } + } + } + + if ($producer === null) $producer = LineReader_command::get(); + if ($consumer === null) $consumer = LineWriter_command::get(); + + return [$producer, $consumer]; + } +} diff --git a/nur_src/mapper/base/CapacitorConsumer.php b/nur_src/mapper/base/CapacitorConsumer.php new file mode 100644 index 0000000..9f45cb2 --- /dev/null +++ b/nur_src/mapper/base/CapacitorConsumer.php @@ -0,0 +1,58 @@ +setCapacitor($capacitor); + } + + /** @var ICapacitor */ + protected $capacitor; + + function setCapacitor(?ICapacitor $capacitor): self { + if ($capacitor === null) $capacitor = new Capacitor(); + $this->capacitor = $capacitor; + $this->setupCapacitor = true; + return $this; + } + + /** @var string|null */ + protected $channel; + + function setChannel(?string $channel): self { + $this->channel = $channel; + return $this; + } + + protected function _setupCapacitor(): void { + } + + /** @var bool */ + protected $setupCapacitor; + + protected function capacitor(): ICapacitor { + if ($this->setupCapacitor) { + $this->_setupCapacitor(); + $this->setupCapacitor = false; + } + return $this->capacitor; + } + + function cook($item) { + $this->capacitor()->charge($item, $this->channel); + } + + function getItem($pkvalues, ?string $channel=null) { + if ($channel === null) $channel = $this->channel; + return $this->capacitor()->getItem($pkvalues, $channel); + } + + function discharge(bool $remove=true, ?string $channel=null): iterable { + if ($channel === null) $channel = $this->channel; + return $this->capacitor()->discharge($channel, $remove); + } +} diff --git a/nur_src/mapper/base/Consumer.php b/nur_src/mapper/base/Consumer.php new file mode 100644 index 0000000..85b6a10 --- /dev/null +++ b/nur_src/mapper/base/Consumer.php @@ -0,0 +1,222 @@ +data = []; + if ($producer !== null) $this->setProducer($producer); + $this->addAll($mappers); + } + + function _haveMethod(string $method): bool { + return method_exists($this, $method); + } + + /** @var iterable */ + private $producer; + + /** @var PushProducer */ + private $pushProducer; + + function setProducer($producer, ...$args): self { + $producer = producer_utils::ensure_producer($producer, $args); + $this->producer = $producer; + $this->pushProducer = $producer instanceof PushProducer? $producer: null; + return $this; + } + + function set($key, $mapper): self { return $this->_set($key, mapper_utils::ensure_mapper_class($mapper)); } + function add($mapper): self { return $this->_set(null, mapper_utils::ensure_mapper_class($mapper)); } + + /** @var bool */ + private $setup = false; + + private function ensureSetup(): bool { + if (!$this->setup) { + $this->setup(); + $this->setup = true; + return true; + } + return false; + } + + protected function setup(): void { + } + + /** @var iterable */ + private $pushIterator; + + private function _ensureSharedOobdManager(iterable $iterator): void { + if ($iterator instanceof IOobdManager) { + # récupérer le gestionnaire partagé le cas échéant + $sharedOobdManager = $iterator->getSharedOobdManager(); + if ($sharedOobdManager !== null) $this->setSharedOobdManager($sharedOobdManager); + } + } + + private function buildIterator(bool $ensurePushable=false): array { + $this->ensureSetup(); + if ($this->pushIterator === null) { + if (!$ensurePushable) { + $iterator = $this->producer; + if ($iterator === null) { + throw new ValueException("a producer is required"); + } + if ($this->pushProducer === null) { + if ($iterator instanceof IOobdManager) { + # s'assurer qu'il y a toujours un gestionnaire partagé + $iterator->ensureSharedOobdManager(); + } + $iterator = mapper_utils::assemble_mappers($this->data, $iterator); + $this->_ensureSharedOobdManager($iterator); + return [true, $iterator]; + } + } + $iterator = $this->pushProducer; + if ($iterator === null) $iterator = new PushProducer(); + # s'assurer qu'il y a toujours un gestionnaire partagé + $iterator->ensureSharedOobdManager(); + $this->pushProducer = $iterator; + $this->pushIterator = mapper_utils::assemble_mappers($this->data, $iterator); + $this->_ensureSharedOobdManager($this->pushIterator); + } + return [false, $this->pushIterator]; + } + + private $iterator; + + function hasOvalue(string $name): bool { + if ($this->_hasOobdValue($name)) { + return true; + } elseif ($this->sharedOobdManager !== null + && $this->sharedOobdManager->hasOvalue($name)) { + return true; + } elseif ($this->iterator instanceof IOobdManager) { + return $this->iterator->hasOvalue($name); + } elseif ($this->pushIterator instanceof IOobdManager) { + return $this->pushIterator->hasOvalue($name); + } + return false; + } + + /** + * c'est la méthode {@link consume()} qui construit $this->iterator. l'accès + * aux données OOB de la chaine de mapper n'est donc valide que dans les + * méthodes {@link _consume()} et {@link cook()} + * + * De même, en mode push, ce n'est qu'après l'appel d'une des méthodes + * {@link ensurePushable()}, {@link push()} ou {@link pushAll()} que les + * données OOB de la chaine de mapper sont disponibles + */ + function getOvalue(string $name, $default=null) { + if ($this->_hasOobdValue($name)) { + return $this->_getOobdValue($name, $default); + } elseif ($this->sharedOobdManager !== null + && $this->sharedOobdManager->hasOvalue($name)) { + return $this->sharedOobdManager->getOvalue($name, $default); + } elseif ($this->iterator instanceof IOobdManager) { + return $this->iterator->getOvalue($name, $default); + } elseif ($this->pushIterator instanceof IOobdManager) { + return $this->pushIterator->getOvalue($name, $default); + } + return null; + } + + function cook($item) { + } + + protected function _consume(iterable $items): void { + $cookFunc = func::_prepare([$this, "cook"]); + foreach ($items as $key => $item) { + func::_call($cookFunc, [$item, $key]); + } + } + + /** + * consommer toutes les valeurs du producer + * + * les méthodes {@link setup()} et {@link teardown()} sont appelées + * automatiquement + */ + function consume($producer=null, ...$mappers): void { + if ($producer !== null) $this->setProducer($producer); + if ($mappers) $this->resetAll($mappers); + + $close = $this->ensureSetup(); + try { + [$close, $iterator] = $this->buildIterator(); + $this->iterator = $iterator; + $this->_consume($iterator); + } finally { + if ($close) $this->close(); + } + } + + protected function teardown(): void { + if ($this->iterator instanceof ICloseable) $this->iterator->close(); + $this->iterator = null; + if ($this->pushIterator !== null) { + if ($this->pushIterator instanceof ICloseable) $this->pushIterator->close(); + $this->pushProducer = null; + $this->pushIterator = null; + } + } + + /** appeler la méthode {@link teardown()} si nécessaire */ + function close(): void { + if ($this->setup) { + $this->teardown(); + $this->setup = false; + } + } + + /** + * s'assurer que les données OOB sont disponibles avant l'utilisation de + * {@link push()} ou {@link pushAll()}. cette méthode n'a pas d'autre utilité + */ + function ensurePushable(): void { + $this->buildIterator(true); + } + + /** + * insérer une valeur, comme si elle provenait du producer. + * + * la méthodes {@link setup()} est appelée automatiquement si nécessaire, mais + * pas {@link teardown()}. il faut donc penser à appeler {@link close()} quand + * on a terminé + */ + function push($item, $key=null): void { + $this->buildIterator(true); + $this->pushProducer->push($item, $key); + $this->_consume($this->pushIterator); + } + + /** + * insérer des valeurs, comme si elles provenaient du producer + */ + function pushAll(iterable $items): void { + $this->buildIterator(true); + $this->pushProducer->pushAll($items); + $this->_consume($this->pushIterator); + } +} diff --git a/nur_src/mapper/base/FuncMapper.php b/nur_src/mapper/base/FuncMapper.php new file mode 100644 index 0000000..7fb0bf1 --- /dev/null +++ b/nur_src/mapper/base/FuncMapper.php @@ -0,0 +1,90 @@ +pp_setFunc($func); + } + + /** @var array */ + protected $func; + + function pp_setFunc(callable $func): void { + $this->func = func::_prepare($func); + } + + /** @var bool */ + protected $ppMarkedOnly; + + const PARAMETRABLE_PARAMS_SCHEMA = [ + "func" => ["callable", null, "fonction à appeler"], + "marked_only" => ["bool", null, "n'appeler la fonction que pour les éléments marqués"], + ]; + + protected function setup(): void { + parent::setup(); + if ($this->ppMarkedOnly === null) { + $this->ppMarkedOnly = mark_utils::is_use_marks($this); + } + } + + function mapper($item, $key=null) { + if (!$this->ppMarkedOnly || $this->isItemMarked($item)) { + $item = func::_call($this->func, [$item, $key, $this]); + } + return $item; + } + + ############################################################################# + const _AUTOGEN_CONSTS = [ + "" => [self::class, "_autogen_consts"], + ]; + const _AUTOGEN_LITERALS = /*autogen*/[ + [ + \nur\b\params\parametrable_utils::class, + '\\nur\\b\\params\\parametrable_utils::class', + ], + [ + self::PARAMETRABLE_PARAMS_SCHEMA, + 'self::PARAMETRABLE_PARAMS_SCHEMA', + ], + ]; + const _AUTOGEN_METHODS = /*autogen*/[ + [ + \nur\b\params\parametrable_utils::class, + '_autogen_methods_getters', + self::PARAMETRABLE_PARAMS_SCHEMA, + null, + ], + [ + \nur\b\params\parametrable_utils::class, + '_autogen_methods_setters', + self::PARAMETRABLE_PARAMS_SCHEMA, + null, + ], + ]; + const _AUTO_GETTERS = /*autogen*/[ + 'getFunc' => 'func', + 'isMarkedOnly' => 'marked_only', + ]; + const _AUTO_SETTERS = /*autogen*/[ + 'setFunc' => 'func', + 'setMarkedOnly' => 'marked_only', + ]; + #--autogen-dynamic-- +} diff --git a/nur_src/mapper/base/FuncMapper_command.php b/nur_src/mapper/base/FuncMapper_command.php new file mode 100644 index 0000000..b7e0b4d --- /dev/null +++ b/nur_src/mapper/base/FuncMapper_command.php @@ -0,0 +1,58 @@ + "appliquer une fonction", + "action" => [self::class, "create_command"], + "cmd_args" => [ + "usage" => self::NAME, + ["-r", "--run-code", "args" => "value", + "action" => [self::class, "set_run_code"], + "help" => "exécuter le code avec eval() pour chaque élément du flux", + ], + ["-f", "--run-file", "args" => "file", + "action" => [self::class, "set_run_file"], + "help" => "exécuter le fichier avec require() pour chaque élément du flux", + ], + ["--mo", "--marked-only", "args" => "value", "type" => "bool", + "action" => [self::class, "set_marked_only"], + "help" => "indiquer si la fonction ne doit être appelée que pour les éléments marqués", + ], + ["-o", "--param", "args" => "value", + "action" => [self::class, "add_params"], + "help" => "spécifier une option générique", + ], + ], + ]; + + /** @var FuncMapper */ + protected static $command; + + static function create_command() { + self::$command = mappers_command::add(new FuncMapper()); + } + + static function set_run_code(string $code) { + self::get()->setFunc(function() use($code) { + return eval($code); + }); + } + + static function set_run_file(string $file) { + self::get()->setFunc(function() use($file) { + return require($file); + }); + } + + static function set_marked_only(?bool $marked_only) { + self::get()->setMarkedOnly($marked_only); + } +} diff --git a/nur_src/mapper/base/Mapper.php b/nur_src/mapper/base/Mapper.php new file mode 100644 index 0000000..a7c947d --- /dev/null +++ b/nur_src/mapper/base/Mapper.php @@ -0,0 +1,362 @@ + null}. cela permet d'implémenter des + * méthodes de mapping plus complexes. + */ + protected function MAP_SOF(): bool { + return static::MAP_SOF; + } const MAP_SOF = false; + + /** + * @return bool indiquer s'il faut appeler {@link mapper()} une dernière fois + * à la fin de l'itération avec {null => null}. cela permet d'implémenter des + * méthodes de mapping plus complexes. + */ + protected function MAP_EOF(): bool { + return static::MAP_EOF; + } const MAP_EOF = false; + + /** + * @param iterable|null $source dans les classes dérivées, cet argument doit + * par convention être le dernier de la liste + */ + function __construct(?iterable $source=null) { + $params = null; + if ($source !== null) $params["source"] = $source; + parent::__construct($params); + } + + /** + * @var array liste de paramètres pour la configuration générique de cet objet + */ + const PARAMETRABLE_PARAMS_SCHEMA = [ + "source" => ["iterable", null, "source de données"], + ]; + + /** @var iterable */ + protected $ppSource; + + function hasOvalue(string $name): bool { + if ($this->_hasOobdValue($name)) { + return true; + } elseif ($this->sharedOobdManager !== null + && $this->sharedOobdManager->hasOvalue($name)) { + return true; + } elseif ($this->ppSource instanceof IOobdManager) { + return $this->ppSource->hasOvalue($name); + } + return false; + } + + function getOvalue(string $name, $default=null) { + if ($this->_hasOobdValue($name)) { + return $this->_getOobdValue($name, $default); + } elseif ($this->sharedOobdManager !== null + && $this->sharedOobdManager->hasOvalue($name)) { + return $this->sharedOobdManager->getOvalue($name, $default); + } elseif ($this->ppSource instanceof IOobdManager) { + return $this->ppSource->getOvalue($name, $default); + } + return null; + } + + /** @var bool */ + private $setup = false; + + function ensureSetup(): void { + if (!$this->setup) { + $this->setup(); + $this->setup = true; + } + } + + /** + * initialiser l'itérateur. cette méthode est appelée avant de lancer + * l'itération + */ + protected function setup(): void { + } + + function getIterator(): iterable { + $this->ensureSetup(); + $this->sof = false; + $this->eof = false; + try { + $mapFunc = func::_prepare([$this, "mapper"]); + $outIndex = 0; + $srcIndex = 0; + if ($this->MAP_SOF()) { + $this->sof = true; + while (true) { + $this->action = self::USE_MAPPED_VALUE_ACTION; + $this->mappedIterables = null; + $mappedValue = func::_call($mapFunc, [null, null]); + if ($this->action !== self::RETRY_ACTION) break; + } + switch ($this->action) { + case self::USE_MAPPED_VALUE_ACTION: + if ($mappedValue !== null) { + yield $outIndex => $mappedValue; + $outIndex++; + } + break; + case self::USE_MAPPED_ITERABLES_ACTION: + foreach ($this->mappedIterables as $mappedIterable) { + $seqIndex = 0; + foreach ($mappedIterable as $mappedKey => $mappedValue) { + if ($mappedKey === $seqIndex) { + # seq + yield $outIndex => $mappedValue; + $outIndex++; + $seqIndex++; + } else { + # assoc + yield $mappedKey => $mappedValue; + } + } + } + break; + } + $this->sof = false; + } + if ($this->ppSource !== null) { + foreach ($this->ppSource as $key => $value) { + while (true) { + $this->mappedIterables = null; + $this->action = self::USE_MAPPED_VALUE_ACTION; + $mappedValue = func::_call($mapFunc, [$value, $key]); + if ($this->action !== self::RETRY_ACTION) break; + } + switch ($this->action) { + case self::USE_MAPPED_VALUE_ACTION: + if ($mappedValue !== null) { + if ($key === $srcIndex) { + yield $outIndex => $mappedValue; + $outIndex++; + } else { + yield $key => $mappedValue; + } + } + break; + case self::USE_MAPPED_ITERABLES_ACTION: + foreach ($this->mappedIterables as $mappedIterable) { + $seqIndex = 0; + foreach ($mappedIterable as $mappedKey => $mappedValue) { + if ($mappedKey === $seqIndex) { + # seq + yield $outIndex => $mappedValue; + $outIndex++; + $seqIndex++; + } else { + # assoc + yield $mappedKey => $mappedValue; + } + } + } + break; + } + if ($key === $srcIndex) $srcIndex++; + } + } + if ($this->MAP_EOF()) { + $this->eof = true; + while (true) { + $this->action = self::USE_MAPPED_VALUE_ACTION; + $this->mappedIterables = null; + $mappedValue = func::_call($mapFunc, [null, null]); + if ($this->action !== self::RETRY_ACTION) break; + } + switch ($this->action) { + case self::USE_MAPPED_VALUE_ACTION: + if ($mappedValue !== null) { + yield $outIndex => $mappedValue; + } + break; + case self::USE_MAPPED_ITERABLES_ACTION: + foreach ($this->mappedIterables as $mappedIterable) { + $seqIndex = 0; + foreach ($mappedIterable as $mappedKey => $mappedValue) { + if ($mappedKey === $seqIndex) { + # seq + yield $outIndex => $mappedValue; + $outIndex++; + $seqIndex++; + } else { + # assoc + yield $mappedKey => $mappedValue; + } + } + } + break; + } + } + } finally { + $this->close(); + } + } + + /** terminer l'itération. cette méthode est appelée à la fin de l'itération */ + protected function teardown(): void { + if ($this->ppSource instanceof ICloseable) $this->ppSource->close(); + } + + function close(): void { + if ($this->setup) { + $this->teardown(); + $this->setup = false; + } + } + + /** + * @var bool indique que l'on est au début de l'itération si et seulement si + * {@link MAP_SOF()} retourne true + */ + protected $sof; + + /** + * @var bool indique que l'on est à la fin de l'itération si et seulement si + * {@link MAP_EOF()} retourne true + */ + protected $eof; + + const USE_MAPPED_VALUE_ACTION = 0; + const USE_MAPPED_ITERABLES_ACTION = 1; + const RETRY_ACTION = 2; + + /** @var bool action à effectuer au retour de la fonction {@link mapper()} */ + private $action; + + /** @var array valeurs générées par les méthodes {@link mapTo()} */ + private $mappedIterables; + + /** + * indiquer que la valeur courante doit être mappée vers $values. si $values + * est null, la valeur courante n'est pas mappée. + * Cette méthode doit être appelée depuis le corps de {@link mapper()} + */ + function mapTo(?iterable $values) { + if ($this->action !== self::USE_MAPPED_ITERABLES_ACTION) { + $this->action = self::USE_MAPPED_ITERABLES_ACTION; + $this->mappedIterables = []; + } + if ($values !== null) { + $this->mappedIterables[] = $values; + } + return null; + } + + /** + * indiquer que la valeur courante doit être mappée vers $value avec + * éventuellement la clé $key. $value est fournie telle quelle: notamment, + * cette méthode peut être utilisée pour mapper la valeur courante vers null. + * Cette méthode doit être appelée depuis le corps de {@link mapper()} + */ + function mapToValue($value, $key=null) { + if ($key !== null) return $this->mapTo([$key => $value]); + else return $this->mapTo([$value]); + } + + /** + * indiquer que la valeur courante n'est pas mappée. + * Cette méthode doit être appelée depuis le corps de {@link mapper()} + */ + function mapToNone() { + return $this->mapTo(null); + } + + /** + * indiquer que la valeur courante ne peut pas être traitée pour le moment et + * qu'elle doit de nouveau être présentée à la fonction {@link mapper()} la + * fois suivante. cela permet d'implémenter des méthodes de mapping plus + * complexes. attention tout de même à ne pas créer de boucles infinies. + * Cette méthode doit être appelée depuis le corps de {@link mapper()} + */ + function retry() { + $this->action = self::RETRY_ACTION; + return null; + } + + /** + * mapper la valeur courante $item. il faut + * - soit retourner la valeur mappée. si on retourne null, la valeur n'est pas + * mappée (il n'y a pas de valeur correspondante, elle est donc ignorée). + * avec cette façon de faire, les clés originales sont conservées + * - soit appeler l'une des méthodes {@link mapTo()}, {@link mapToValue()}, + * {@link mapToNone()} pour indiquer la ou les valeurs correspondantes (ainsi + * que les clés correspondantes le cas échéant). avec cette façon de faire, + * les clés originales sont perdues + * + * Dans les classes dérivées, il est possible de rajouter "$key=null" à la + * signature de cette fonction pour avoir aussi la clé + */ + abstract function mapper($item); + + ############################################################################# + const _AUTOGEN_CONSTS = [ + "" => [self::class, "_autogen_consts"], + ]; + const _AUTOGEN_LITERALS = /*autogen*/[ + [ + \nur\b\params\parametrable_utils::class, + '\\nur\\b\\params\\parametrable_utils::class', + ], + [ + self::PARAMETRABLE_PARAMS_SCHEMA, + 'self::PARAMETRABLE_PARAMS_SCHEMA', + ], + ]; + const _AUTOGEN_METHODS = /*autogen*/[ + [ + \nur\b\params\parametrable_utils::class, + '_autogen_methods_getters', + self::PARAMETRABLE_PARAMS_SCHEMA, + null, + ], + [ + \nur\b\params\parametrable_utils::class, + '_autogen_methods_setters', + self::PARAMETRABLE_PARAMS_SCHEMA, + null, + ], + ]; + const _AUTO_GETTERS = /*autogen*/[ + 'getSource' => 'source', + ]; + const _AUTO_SETTERS = /*autogen*/[ + 'setSource' => 'source', + ]; + #--autogen-dynamic-- +} diff --git a/nur_src/mapper/base/MapperAggregate.php b/nur_src/mapper/base/MapperAggregate.php new file mode 100644 index 0000000..cfd050f --- /dev/null +++ b/nur_src/mapper/base/MapperAggregate.php @@ -0,0 +1,71 @@ +_set($key, mapper_utils::ensure_mapper_class($mapper)); } + function add($mapper): self { return $this->_set(null, mapper_utils::ensure_mapper_class($mapper)); } + + function setup(): void { + parent::setup(); + $this->producer = new PushProducer(); + $this->iterator = mapper_utils::assemble_mappers($this->data, $this->producer); + } + + /** @var PushProducer */ + private $producer; + + /** @var Mapper */ + private $iterator; + + function hasOvalue(string $name): bool { + if ($this->_hasOobdValue($name)) { + return true; + } elseif ($this->sharedOobdManager !== null + && $this->sharedOobdManager->hasOvalue($name)) { + return true; + } elseif ($this->iterator instanceof IOobdManager) { + return $this->iterator->hasOvalue($name); + } + return false; + } + + /** + * c'est la méthode {@link setup()} qui contruit $this->iterator. l'accès + * aux données OOB de la chaine de mapper n'est donc possible que dans la + * méthodes {@link mapper()} + */ + function getOvalue(string $name, $default=null) { + if ($this->_hasOobdValue($name)) { + return $this->_getOobdValue($name, $default); + } elseif ($this->sharedOobdManager !== null + && $this->sharedOobdManager->hasOvalue($name)) { + return $this->sharedOobdManager->getOvalue($name, $default); + } elseif ($this->iterator instanceof IOobdManager) { + return $this->iterator->getOvalue($name, $default); + } + return null; + } + + function mapper($item) { + $this->producer->push($item); + return $this->mapTo($this->iterator); + } + + function teardown(): void { + parent::teardown(); + $this->producer->close(); + $this->producer = null; + $this->iterator->close(); + $this->iterator = null; + } +} diff --git a/nur_src/mapper/base/Producer.php b/nur_src/mapper/base/Producer.php new file mode 100644 index 0000000..18df597 --- /dev/null +++ b/nur_src/mapper/base/Producer.php @@ -0,0 +1,88 @@ +close(); + * } + * ~~~ + */ +abstract class Producer extends Parametrable implements IteratorAggregate, ICloseable, IParametrable, IOobdManager { + use TOobdManager; + + /** + * initialiser le générateur. cette méthode est appelée avant de lancer + * la génération + */ + protected function setup(): void { + } + + /** + * terminer le générateur. cette méthode est appelée à la fin de la génération + */ + protected function teardown(): void { + } + + /** + * retourner un générateur qui produit les données. ce générateur doit être + * préparé à prendre une exception {@link StopException} à chaque occurrence + * de yield + */ + abstract function producer(); + + /** @var Generator */ + private $generator; + + function getIterator() { + $this->setup(); + $this->return = null; + return $this->generator = $this->producer(); + } + + function close(): void { + if ($this->generator !== null) { + if ($this->generator instanceof Generator) { + try { + $this->generator->throw(new StopException()); + $hasReturned = true; + } catch (StopException $e) { + $hasReturned = false; + } + $this->return = $hasReturned? $this->generator->getReturn(): null; + } + $this->generator = null; + } + $this->teardown(); + } + + protected $return; + + /** + * retourner la valeur de retour du générateur. il faut d'abord appeler la + * méthode {@link close()} + */ + function getReturn() { + return $this->return; + } +} diff --git a/nur_src/mapper/base/ProducerAggregate.php b/nur_src/mapper/base/ProducerAggregate.php new file mode 100644 index 0000000..cd59022 --- /dev/null +++ b/nur_src/mapper/base/ProducerAggregate.php @@ -0,0 +1,78 @@ +data as &$producer) { + $producer = producer_utils::ensure_producer($producer); + }; unset($producer); + } + + function hasOvalue(string $name): bool { + if ($this->_hasOobdValue($name)) { + return true; + } elseif ($this->sharedOobdManager !== null + && $this->sharedOobdManager->hasOvalue($name)) { + return true; + } + foreach ($this->data as $producer) { + if ($producer instanceof IOobdManager && $producer->hasOvalue($name)) { + return true; + } + } + return false; + } + + /** + * l'accès aux données OOB de la chaine de producer n'est possible qu'après + * l'appel de la méthode {@link setup()} + */ + function getOvalue(string $name, $default=null) { + if ($this->_hasOobdValue($name)) { + return $this->_getOobdValue($name, $default); + } elseif ($this->sharedOobdManager !== null + && $this->sharedOobdManager->hasOvalue($name)) { + return $this->sharedOobdManager->getOvalue($name, $default); + } + foreach ($this->data as $producer) { + if ($producer instanceof IOobdManager && $producer->hasOvalue($name)) { + return $producer->getOvalue($name, $default); + } + } + return null; + } + + function producer() { + foreach ($this->data as $producer) { + if ($producer instanceof Producer) { + try { + yield from $producer; + } finally { + $producer->close(); + } + $this->return = $producer->getReturn(); + } else { + yield from $producer; + } + } + } + + protected function teardown(): void { + parent::teardown(); + foreach ($this->data as $producer) { + $producer->close(); + } + } +} diff --git a/nur_src/mapper/base/PushProducer.php b/nur_src/mapper/base/PushProducer.php new file mode 100644 index 0000000..437f6d7 --- /dev/null +++ b/nur_src/mapper/base/PushProducer.php @@ -0,0 +1,33 @@ + $value]: [$value]; + A::append($this->iterables, $values); + } + + function pushAll(iterable $values): void { + A::append($this->iterables, $values); + } + + function producer() { + if ($this->iterables !== null) { + try { + foreach ($this->iterables as $iterable) { + yield from $iterable; + } + } finally { + $this->iterables = null; + } + } + } +} diff --git a/nur_src/mapper/base/StopException.php b/nur_src/mapper/base/StopException.php new file mode 100644 index 0000000..56cd84f --- /dev/null +++ b/nur_src/mapper/base/StopException.php @@ -0,0 +1,14 @@ +ppInputEncoding: null; + $output_encoding = in_array("output_encoding", $modifiedKeys)? $this->ppOutputEncoding: null; + if ($input_encoding !== null) { + $args = [$input_encoding]; + if ($output_encoding !== null) $args[] = $output_encoding; + $this->setEncodingFilter(...$args); + } + } + + protected function encodingOutput__afterSetParametrableParams(array $modifiedKeys): void { + $output_encoding = in_array("output_encoding", $modifiedKeys)? $this->ppOutputEncoding: null; + $input_encoding = in_array("input_encoding", $modifiedKeys)? $this->ppInputEncoding: null; + if ($output_encoding !== null) { + $args = [$output_encoding]; + if ($input_encoding !== null) $args[] = $input_encoding; + $this->setEncodingFilter(...$args); + } + } +} diff --git a/nur_src/mapper/base/Tparams_command.php b/nur_src/mapper/base/Tparams_command.php new file mode 100644 index 0000000..1297956 --- /dev/null +++ b/nur_src/mapper/base/Tparams_command.php @@ -0,0 +1,24 @@ +setParametrableParams([$name => $value]); + } + } +} diff --git a/nur_src/mapper/base/Ttmpwriter.php b/nur_src/mapper/base/Ttmpwriter.php new file mode 100644 index 0000000..6d67327 --- /dev/null +++ b/nur_src/mapper/base/Ttmpwriter.php @@ -0,0 +1,74 @@ +writer = $output; + else $this->ppOutput = $output; + } + + protected $useTmpfile; + + /** + * utiliser un fichier temporaire pour la sortie, puis créer $output en une + * seule fois à la fin, lors de la fermeture de l'objet. + * + * $useTmpfile peut être un booléen, mais aussi un nom de fichier: dans ce + * cas la fonctionnalité n'est activée que si $useTmpfile et $output + * désignent le même fichier + */ + function setUseTmpfile($useTmpfile=true): self { + if (is_string($useTmpfile)) { + if (writer::is_file($useTmpfile)) { + $useTmpfile = realpath($useTmpfile) === realpath($this->ppOutput); + } else { + $useTmpfile = false; + } + } + $this->useTmpfile = boolval($useTmpfile); + return $this; + } + + /** @var IWriter */ + protected $writer; + + protected function buildWriter(bool $appendFilters, ?string $mode): void { + if ($mode === null) $mode = "w+b"; + $this->writer = writer::with($this->ppOutput, $mode); + if ($appendFilters) $this->_rwAppendFilters($this->writer); + } + + /** @var TmpfileWriter */ + protected $tmpwriter; + + protected function getWriter() { + return $this->useTmpfile? $this->tmpwriter: $this->writer; + } + + protected function setupWriter(bool $useFilters, ?string $mode=null): void { + if ($this->useTmpfile) { + $this->tmpwriter = new TmpfileWriter(); + } else if ($this->writer === null) { + $this->buildWriter($useFilters, $mode); + } + } + + protected function teardownWriter(bool $useFilters, ?string $mode=null): void { + if ($this->tmpwriter !== null) { + $this->tmpwriter->close(); + $this->buildWriter($useFilters, $mode); + $this->tmpwriter->getReader()->copyTo($this->writer); + $this->tmpwriter = null; + } + if ($this->writer !== null) { + $this->writer->close(); + $this->writer = null; + } + } +} diff --git a/nur_src/mapper/base/capacitor/Capacitor.php b/nur_src/mapper/base/capacitor/Capacitor.php new file mode 100644 index 0000000..1206fba --- /dev/null +++ b/nur_src/mapper/base/capacitor/Capacitor.php @@ -0,0 +1,145 @@ +eachEnsureSchema($keys); + $namedkeys = []; + $pkeys = null; + foreach ($keys as $key) { + $namedkeys[$key["name"]] = $key; + if ($key["primary"]) $pkeys[] = $key; + } + $this->kinfos[$channel] = ["keys" => $namedkeys, "pkeys" => $pkeys]; + } + + private static function get_kvalues(array $keys, $item): array { + $item = A::with($item); + $kvalues = []; + foreach ($keys as $kname => $key) { + $kvalues[$kname] = A::get($item, $key["name"]); + } + return $kvalues; + } + + private static function compute_itemkey(array $pkvalues): string { + return implode("-", $pkvalues); + } + + private static function get_itemkey(?array $kinfos, $item): ?string { + if ($kinfos === null) return null; + $pkeys = $kinfos["pkeys"]; + if ($pkeys === null) return null; + $pkvalues = self::get_kvalues($pkeys, $item); + return self::compute_itemkey($pkvalues); + } + + /** @var array */ + protected $data; + + /** + * trier les données selon les clés spécifiées. NB: cette implémentation + * autorise qu'on utilise des clés qui n'ont pas été déclarées avec + * {@link setKeys()} + * + * @see ICapacitor::sort() + */ + function sort(?array $keys=null, ?string $channel=null): void { + $kinfos = A::get($this->kinfos, $channel); + $asort = $kinfos !== null && $kinfos["pkeys"] !== null; + $defaultKeys = $kinfos !== null? $kinfos["keys"]: null; + if ($keys !== null) { + self::sort_md()->eachEnsureSchema($keys); + } else { + $keys = $defaultKeys; + if ($keys === null) return; + } + foreach ($keys as $kname => &$key) { + if ($key["reverse"] === null && $defaultKeys !== null && array_key_exists($kname, $defaultKeys)) { + $key["reverse"] = $defaultKeys[$kname]["reverse"]; + } + if ($key["reverse"] === null) $key["reverse"] = false; + }; unset($key); + $sortfunc = function ($a, $b) use ($keys) { + $akvs = self::get_kvalues($keys, $a); + $bkvs = self::get_kvalues($keys, $b); + foreach ($keys as $kname => $key) { + $akv = $akvs[$kname]; + $bkv = $bkvs[$kname]; + if ($akv == $bkv) continue; + if ($akv < $bkv) { + return $key["reverse"] ? 1 : -1; + } else { + return $key["reverse"] ? -1 : 1; + } + } + return 0; + }; + if ($asort) uasort($this->data[$channel], $sortfunc); + else usort($this->data[$channel], $sortfunc); + } + + function charge($item, ?string $channel=null, $pkvalues=null): void { + $kinfos = A::get($this->kinfos, $channel); + $itemkey = self::get_itemkey($kinfos, $item); + if ($itemkey === null && $item === null) { + if ($pkvalues === null) return; + $itemkey = self::compute_itemkey($pkvalues); + } + A::set($this->data[$channel], $itemkey, $item); + } + + function getItem($pkvalues, ?string $channel=null, $default=null) { + if (is_array($pkvalues)) { + $itemkey = self::compute_itemkey($pkvalues); + } else { + $itemkey = $pkvalues; + } + return A::_pget($this->data, [$channel, $itemkey], $default); + } + + function discharge(?string $channel=null, bool $remove=true): iterable { + $items = A::get($this->data, $channel, []); + if ($remove) A::del($this->data, $channel); + return $items; + } +} diff --git a/nur_src/mapper/base/capacitor/ICapacitor.php b/nur_src/mapper/base/capacitor/ICapacitor.php new file mode 100644 index 0000000..2b6a9e2 --- /dev/null +++ b/nur_src/mapper/base/capacitor/ICapacitor.php @@ -0,0 +1,62 @@ + ["key", null, "nom de la clé", "required" => true], + "primary" => ["bool", false, "est-ce une clé primaire?"], + "reverse" => ["?bool", null, "sens du tri par défaut"], + ]; + + /** + * spécifier les clés à traquer lors du chargement d'une donnée. + * + * si une clé est marquée comme primaire, alors les doublons éventuels sont + * supprimés au fur et à mesure du chargement. + */ + function setKeys(array $keys, ?string $channel=null): void; + + const SORT_SCHEMA = [ + "name" => ["key", null, "nom de la clé", "required" => true], + "reverse" => ["?bool", null, "sens du tri"], + ]; + + /** + * trier les données selon les clés spécifiées. si $keys===null, alors toutes + * les clés définies au préalable avec {@link setKeys()} sont utilisées. + * + * $keys ne doit mentionner que des clés déclarées avec {@link setKeys()} + */ + function sort(?array $keys=null, ?string $channel=null): void; + + /** + * charger l'accumulateur avec un élément. + * + * Dans le cas où $item === null, le comportement est particulier: + * - Si aucune clé primaire n'a été définie, l'élément est systématiquement + * chargé + * - Si une clé primaire a été définie, l'élément est ignoré, sauf si + * $pkvalues est spécifiée. Dans ce cas, $pkvalues est utilisé comme clé + * primaire + */ + function charge($item, ?string $channel=null, $pkvalues=null): void; + + /** méthode de convenance pour charger l'accumulateur avec plusieurs éléments */ + function chargeAll(iterable $items, ?string $channel=null): void; + + /** + * obtenir un élément à partir de sa clé primaire. une clé primaire doit avoir + * été définie au préalable avec {@link setKeys()} + * + * si aucune clé primaire n'a été définie, alors le seul moyen d'atteindre un + * élément est par son index + */ + function getItem($pkvalues, ?string $channel=null, $default=null); + + /** décharger l'accumulateur en une seule fois */ + function discharge(?string $channel=null, bool $remove=true): iterable; +} diff --git a/nur_src/mapper/base/capacitor/SqliteCapacitor.php b/nur_src/mapper/base/capacitor/SqliteCapacitor.php new file mode 100644 index 0000000..12749b0 --- /dev/null +++ b/nur_src/mapper/base/capacitor/SqliteCapacitor.php @@ -0,0 +1,30 @@ +charge($item, $channel); + } + } + + function offsetExists($offset) { throw IllegalAccessException::not_implemented(); } + function offsetGet($offset) { throw IllegalAccessException::not_implemented(); } + function offsetUnset($offset) { throw IllegalAccessException::not_implemented(); } + function offsetSet($offset, $value) { $this->charge($value); } +} diff --git a/nur_src/mapper/base/encoding_utils.php b/nur_src/mapper/base/encoding_utils.php new file mode 100644 index 0000000..f6de294 --- /dev/null +++ b/nur_src/mapper/base/encoding_utils.php @@ -0,0 +1,9 @@ + ["?string", null, "encoding en entrée"], + "output_encoding" => ["?string", null, "encoding en sortie"], + ]; +} diff --git a/nur_src/mapper/base/mapper_utils.php b/nur_src/mapper/base/mapper_utils.php new file mode 100644 index 0000000..ddadc81 --- /dev/null +++ b/nur_src/mapper/base/mapper_utils.php @@ -0,0 +1,91 @@ +setSource($iterator); + } elseif (is_callable($mapper)) { + $mapper = new FuncMapper($mapper, $iterator); + } else { + if (!is_array($mapper)) $mapper = [$mapper]; + [$args, $params] = A::split_assoc($mapper); + if ($args === null) throw new ValueException("mapper class is required"); + $c = new ReflectionClass($args[0]); + $rf = $c->getConstructor(); + if ($rf !== null && !$rf->isVariadic()) { + # NB: $maxArgs ne doit pas tenir compte du dernier argument $source, + # mais comme le premier élément de $args est la classe, ça colle + $maxArgs = $rf->getNumberOfParameters(); + $args = array_slice($mapper, 0, $maxArgs); + while (count($args) < $maxArgs) { + $args[] = null; + } + } + $args[] = $iterator; + $mapper = func::cons(...$args); + if ($params !== null) $mapper->setParametrableParams($params); + } + if ($iterator instanceof IOobdManager) { + # récupérer le gestionnaire partagé le cas échéant + $sharedOobdManager = $iterator->getSharedOobdManager(); + if ($sharedOobdManager !== null) $mapper->setSharedOobdManager($sharedOobdManager); + } + return $mapper; + } + + static function assemble_mappers(array $mappers, iterable $iterator): iterable { + foreach ($mappers as $mapper) { + $iterator = self::ensure_mapper($mapper, $iterator); + } + return $iterator; + } + + public static function check_prefix(string $value, ?string &$params, string ...$prefixes): bool { + foreach ($prefixes as $prefix) { + if (preg_match('/^[A-Za-z0-9_]+$/', $prefix)) { + # correspondance exacte pour une commande alpha-numérique + if ($value == $prefix) { + $params = null; + return true; + } + } elseif (str::_starts_with($prefix, $value)) { + $params = str::without_prefix($prefix, $value); + return true; + } + } + return false; + } + + static function split_param(string $param, ?string &$name, ?string &$type, ?string &$value): bool { + if (preg_match('/^([A-Za-z0-9_]*)(?::([^=]+))?(?:=(.*))?$/', $param, $vs)) { + $name = base::vn(A::get($vs, 1)); + $type = base::vn(A::get($vs, 2)); + $value = base::vn(A::get($vs, 3)); + return true; + } + return false; + } +} diff --git a/nur_src/mapper/base/mappers_command.php b/nur_src/mapper/base/mappers_command.php new file mode 100644 index 0000000..690f88a --- /dev/null +++ b/nur_src/mapper/base/mappers_command.php @@ -0,0 +1,72 @@ +DYNAMIC_COMMAND_CLASSES() as $cc) { + $commands[] = $cc::get_def(); + } + return $commands; + } + + static $mappers = []; + + static function add(Mapper $mapper): Mapper { + self::$mappers[] = $mapper; + return $mapper; + } +} diff --git a/nur_src/mapper/base/mark_utils.php b/nur_src/mapper/base/mark_utils.php new file mode 100644 index 0000000..5178a9c --- /dev/null +++ b/nur_src/mapper/base/mark_utils.php @@ -0,0 +1,48 @@ +setOvalue("mark:$prefix:have_marks", true, $shared); + } + + static function is_use_marks(IOobdManager $manager, ?string $prefix=null): bool { + return boolval($manager->getOvalue("mark:$prefix:have_marks")); + } + + static function set_use_keys_for_item_marked(IOobdManager $manager, ?array $keys, ?string $prefix=null): void { + $manager->setOvalue("mark:$prefix:keys", $keys); + } + + static function set_item_marked(IOobdManager $manager, $item, ?string $prefix=null, bool $shared=true): void { + $keys = $manager->getOvalue("mark:$prefix:keys"); + $mark = self::compute($item, $keys); + $manager->setOvalue("mark:$prefix:marks:$mark", true, $shared); + $manager->setOvalue("mark:$prefix:have_marks", true, $shared); + } + + static function is_item_marked(IOobdManager $manager, $item, ?string $prefix=null): bool { + $keys = $manager->getOvalue("mark:$prefix:keys"); + $mark = self::compute($item, $keys); + return boolval($manager->getOvalue("mark:$prefix:marks:$mark")); + } +} diff --git a/nur_src/mapper/base/oobd/IOobdManager.php b/nur_src/mapper/base/oobd/IOobdManager.php new file mode 100644 index 0000000..9fcfc79 --- /dev/null +++ b/nur_src/mapper/base/oobd/IOobdManager.php @@ -0,0 +1,72 @@ +oobdValues; + $values = null; + foreach ($this->oobdValues as $key => $value) { + if (is_string($key) && str::_starts_with($keyPrefix, $key)) { + $key = str::without_prefix($keyPrefix, $key); + $values[$key] = $value; + } + } + return $values; + } +} diff --git a/nur_src/mapper/base/oobd/TOobdManager.php b/nur_src/mapper/base/oobd/TOobdManager.php new file mode 100644 index 0000000..f4c18d9 --- /dev/null +++ b/nur_src/mapper/base/oobd/TOobdManager.php @@ -0,0 +1,119 @@ +sharedOobdManager; + } + + function setSharedOobdManager(IOobdManager $sharedOobdManager): self { + $this->sharedOobdManager = $sharedOobdManager; + return $this; + } + + function ensureSharedOobdManager(): void { + if ($this->sharedOobdManager === null) { + $this->sharedOobdManager = new OobdManager(); + } + } + + protected $oobdValues; + + protected function _hasOobdValue(string $name): bool { + return A::has($this->oobdValues, $name); + } + + protected function _getOobdValue(string $name, $default) { + return A::get($this->oobdValues, $name, $default); + } + + protected function _setOobdValue(string $name, $value): void { + $this->oobdValues[$name] = $value; + } + + protected function _unsetOobdValue(string $name): void { + unset($this->oobdValues[$name]); + } + + function hasOvalue(string $name): bool { + if ($this->_hasOobdValue($name)) { + return true; + } elseif ($this->sharedOobdManager !== null + && $this->sharedOobdManager->hasOvalue($name)) { + return true; + } + return false; + } + + function getOvalue(string $name, $default=null) { + if ($this->_hasOobdValue($name)) { + return $this->_getOobdValue($name, $default); + } elseif ($this->sharedOobdManager !== null + && $this->sharedOobdManager->hasOvalue($name)) { + return $this->sharedOobdManager->getOvalue($name, $default); + } + return null; + } + + function setOvalue(string $name, $value, bool $shared=true) { + if ($shared && $this->sharedOobdManager !== null) { + return $this->sharedOobdManager->setOvalue($name, $value, false); + } else { + $oldValue = $this->_getOobdValue($name, null); + if (is_bool($value) && $oldValue === true) { + $value = $oldValue; + } else { + $this->_setOobdValue($name, $value); + } + return $value; + } + } + + function incOvalue(string $name, bool $shared=true): int { + if ($shared && $this->sharedOobdManager !== null) { + return $this->sharedOobdManager->incOvalue($name, false); + } else { + $value = intval($this->_getOobdValue($name, 0)) + 1; + $this->_setOobdValue($name, $value); + return $value; + } + } + + function decOvalue(string $name, bool $shared=true): int { + if ($shared && $this->sharedOobdManager !== null) { + return $this->sharedOobdManager->incOvalue($name, false); + } else { + $value = intval($this->_getOobdValue($name, 0)) - 1; + $this->_setOobdValue($name, $value); + return $value; + } + } + + function resetOvalue(string $name, $value=null, bool $shared=true) { + if ($shared && $this->sharedOobdManager !== null) { + return $this->sharedOobdManager->resetOvalue($name, $value, false); + } else { + if ($value === null) $this->_unsetOobdValue($name); + else $this->_setOobdValue($name, $value); + return $value; + } + } + + function setUseKeysForItemMarked(?array $keys, ?string $prefix=null): void { + mark_utils::set_use_keys_for_item_marked($this, $keys, $prefix); + } + + function setItemMarked($item, ?string $prefix=null, bool $shared=true): void { + mark_utils::set_item_marked($this, $item, $prefix, $shared); + } + + function isItemMarked($item, ?string $prefix=null): bool { + return mark_utils::is_item_marked($this, $item, $prefix); + } +} diff --git a/nur_src/mapper/base/producer_utils.php b/nur_src/mapper/base/producer_utils.php new file mode 100644 index 0000000..2d3c414 --- /dev/null +++ b/nur_src/mapper/base/producer_utils.php @@ -0,0 +1,23 @@ +separator = static::SEPARATOR; + $this->enclosure = static::ENCLOSURE; + $this->escape = static::ESCAPE; + } + + const PARAMETRABLE_PARAMS_SCHEMA = [ + "csv_flavour" => [null, null, "type de fichier CSV: excel ou ooffice"], + "multi_schema" => ["bool", false, "les flux multi-schémas sont-ils supportés?"], + ]; + + protected $separator; + protected $enclosure; + protected $escape; + + function getCsvFlavour(): array { + return [$this->separator, $this->enclosure, $this->escape]; + } + + /** @param $csvFlavour string|array */ + function pp_setCsvFlavour($csvFlavour): void { + if ($csvFlavour === null) { + $csvFlavour = [static::SEPARATOR, static::ENCLOSURE, static::ESCAPE]; + } + if (is_string($csvFlavour)) { + $csvFlavour = csv_defaults::verifix_name($csvFlavour); + switch ($csvFlavour) { + case csv_defaults::OO_NAME: + $csvFlavour = csv_defaults::OO_FLAVOUR; + break; + case csv_defaults::XL_NAME: + $csvFlavour = csv_defaults::XL_FLAVOUR; + break; + default: + if (strlen($csvFlavour) <= 3) { + $separator = substr($csvFlavour, 0, 1); + if (!$separator) $separator = static::SEPARATOR; + $enclosure = substr($csvFlavour, 1, 1); + if (!$enclosure) $enclosure = static::ENCLOSURE; + $escape = substr($csvFlavour, 2, 1); + if (!$escape) $escape = static::ESCAPE; + $csvFlavour = [$separator, $enclosure, $escape]; + } + break; + } + } + if (is_array($csvFlavour)) { + [$this->separator, $this->enclosure, $this->escape] = $csvFlavour; + } else { + throw ValueException::invalid_value($csvFlavour, "csv flavour"); + } + } + + /** @param $csvFlavour string|array */ + function setCsvFlavour($csvFlavour): self { + $this->pp_setCsvFlavour($csvFlavour); + return $this; + } + + /** @var bool les fichiers avec plusieurs schémas sont-ils supportés? */ + protected $ppMultiSchema; + + function setMultiSchema(bool $multiSchema=true): self { + $this->ppMultiSchema = $multiSchema; + return $this; + } +} diff --git a/nur_src/mapper/csv/Assoc2CsvMapper.php b/nur_src/mapper/csv/Assoc2CsvMapper.php new file mode 100644 index 0000000..8281ed8 --- /dev/null +++ b/nur_src/mapper/csv/Assoc2CsvMapper.php @@ -0,0 +1,153 @@ + ["array", null, "liste et ordre des champs en sortie"], + "output_headers" => ["bool", true, "faut-il afficher les en-têtes en sortie?"], + ]; + + /** + * @var array liste et ordre des champs en sortie. si cette valeur n'est pas + * spécifiée, elle est calculée à partir du premier élément du flux. + */ + protected $ppHeaders; + + /** @var bool faut-il afficher les en-têtes en sortie? */ + protected $ppOutputHeaders = true; + + private static final function is_different(array $h1, array $h2): bool { + sort($h1); + sort($h2); + return $h1 != $h2; + } + + private function computeHeaders(array $row) { + return array_keys($row); + } + + function _checkHeaders(?array $row): array { + $skipLine = false; + if ($row === null) { + $outputHeaders = $this->ppOutputHeaders && $this->ppHeaders !== null; + return [$skipLine, $outputHeaders, $this->ppHeaders]; + } + $prevHeaders = $this->ppHeaders; + if ($this->ppMultiSchema) { + # vérifier si le schéma a changé + $headers = $this->computeHeaders($row); + if ($prevHeaders === null) $prevHeaders = $headers; + if (self::is_different($prevHeaders, $headers)) { + $skipLine = true; + $this->ppOutputHeaders = true; + } else { + $headers = $prevHeaders; + } + } else { + $headers = $prevHeaders; + if ($headers === null) $headers = $this->computeHeaders($row); + } + return [$skipLine, $this->ppOutputHeaders, $headers]; + } + + function _setOutputHeaders(array $headers): void { + $this->ppHeaders = $headers; + $this->ppOutputHeaders = false; + } + + function _cookHeaders(array $headers): array { + return $headers; + } + + function _cookValues(array $headers, array $row): array { + $values = []; + foreach ($headers as $header) { + $values[] = A::get($row, $header, false); + } + return $values; + } + + function getLine(array $values, bool $stripNl=true): string { + $tmpf = fopen("php://memory", "w+b"); + fputcsv($tmpf, $values, $this->separator, $this->enclosure, $this->escape); + rewind($tmpf); + $line = stream_get_contents($tmpf); + if ($stripNl) $line = str::strip_nl($line); + fclose($tmpf); + return $line; + } + + function mapper($item) { + if ($this->eof) $row = null; + else $row = A::with($item); + $lines = []; + + [$skipLine, $outputHeaders, $headers] = $this->_checkHeaders($row); + if ($skipLine) $lines[] = ""; + if ($outputHeaders) { + $lines[] = $this->getLine($this->_cookHeaders($headers)); + $this->_setOutputHeaders($headers); + } + if ($row !== null) { + $lines[] = $this->getLine($this->_cookValues($headers, $row)); + } + + $this->mapTo($lines); + } + + ############################################################################# + const _AUTOGEN_CONSTS = [ + "" => [self::class, "_autogen_consts"], + ]; + const _AUTOGEN_LITERALS = /*autogen*/[ + [ + \nur\b\params\parametrable_utils::class, + '\\nur\\b\\params\\parametrable_utils::class', + ], + [ + self::PARAMETRABLE_PARAMS_SCHEMA, + 'self::PARAMETRABLE_PARAMS_SCHEMA', + ], + ]; + const _AUTOGEN_METHODS = /*autogen*/[ + [ + \nur\b\params\parametrable_utils::class, + '_autogen_methods_getters', + self::PARAMETRABLE_PARAMS_SCHEMA, + null, + ], + [ + \nur\b\params\parametrable_utils::class, + '_autogen_methods_setters', + self::PARAMETRABLE_PARAMS_SCHEMA, + null, + ], + ]; + const _AUTO_GETTERS = /*autogen*/[ + 'getHeaders' => 'headers', + 'isOutputHeaders' => 'output_headers', + ]; + const _AUTO_SETTERS = /*autogen*/[ + 'setHeaders' => 'headers', + 'setOutputHeaders' => 'output_headers', + ]; + #--autogen-dynamic-- +} diff --git a/nur_src/mapper/csv/Assoc2CsvMapper_command.php b/nur_src/mapper/csv/Assoc2CsvMapper_command.php new file mode 100644 index 0000000..63b8281 --- /dev/null +++ b/nur_src/mapper/csv/Assoc2CsvMapper_command.php @@ -0,0 +1,33 @@ + "convertir en CSV", + "action" => [self::class, "create_command"], + "cmd_args" => [ + "usage" => self::NAME, + ["-o", "--param", "args" => "value", + "action" => [self::class, "add_params"], + "help" => "spécifier une option générique", + ], + ], + ]; + + /** @var Assoc2CsvMapper */ + protected static $command; + + static function create_command() { + self::$command = mappers_command::add(new Assoc2CsvMapper()); + } +} diff --git a/nur_src/mapper/csv/Csv2AssocMapper.php b/nur_src/mapper/csv/Csv2AssocMapper.php new file mode 100644 index 0000000..5ea50fe --- /dev/null +++ b/nur_src/mapper/csv/Csv2AssocMapper.php @@ -0,0 +1,145 @@ +seq2assoc = new Seq2AssocMapper(); + parent::__construct($source); + } + + const PARAMETRABLE_PARAMS_SCHEMA = [ + "parse_headers" => ["?bool", null, "faut-il analyser le premier élément du flux pour calculer la liste des en-têtes en entrée?"], + "headers" => ["?array", null, "liste et ordre des en-têtes en entrée"], + "skip_lines" => ["int", 0, "nombre de lignes à sauter en entrée"], + "map_empty" => ["?string", null, "valeur adoptée pour les chaines vides"], + ]; + + /** @var Seq2AssocMapper */ + private $seq2assoc; + function pp_setParseHeaders(?bool $parseHeaders): void { + $this->seq2assoc->setParseKeys($parseHeaders); + } + function pp_setHeaders(?array $headers): void { + $this->seq2assoc->setKeys($headers); + } + + /** @var int */ + protected $ppSkipLines; + + /** @var bool */ + protected $shouldMapEmpty = false; + + protected $ppMapEmpty = null; + + function pp_setMapEmpty($mapEmpty=null): void { + $this->shouldMapEmpty = true; + $this->ppMapEmpty = $mapEmpty; + } + + protected function setup(): void { + parent::setup(); + $this->seq2assoc->ensureSetup(); + } + + function _parseLine(): bool { + if ($this->ppSkipLines > 0) { + $this->ppSkipLines--; + return false; + } + return true; + } + + /** @var array */ + private $headers; + + function _checkHeader(array $values): bool { + if ($this->ppMultiSchema && count($values) === 1 && $values[0] === null) { + # ligne vide, changer de schéma + $this->seq2assoc->setKeys(null); + return true; + } + return $this->seq2assoc->_checkKeys($values); + } + + function _mapRow(array $values): array { + $row = $this->seq2assoc->_mapValues($values); + foreach ($row as &$value) { + if ($value === "" && $this->shouldMapEmpty) $value = $this->ppMapEmpty; + }; unset($value); + return $row; + } + + function mapper($item) { + if (!$this->_parseLine()) return $this->mapToNone(); + $values = str_getcsv($item, $this->separator, $this->enclosure, $this->escape); + if ($this->_checkHeader($values)) return $this->mapToNone(); + return $this->_mapRow($values); + } + + protected function teardown(): void { + parent::teardown(); + $this->seq2assoc->close(); + } + + ############################################################################# + const _AUTOGEN_CONSTS = [ + "" => [self::class, "_autogen_consts"], + ]; + const _AUTOGEN_LITERALS = /*autogen*/[ + [ + \nur\b\params\parametrable_utils::class, + '\\nur\\b\\params\\parametrable_utils::class', + ], + [ + self::PARAMETRABLE_PARAMS_SCHEMA, + 'self::PARAMETRABLE_PARAMS_SCHEMA', + ], + ]; + const _AUTOGEN_METHODS = /*autogen*/[ + [ + \nur\b\params\parametrable_utils::class, + '_autogen_methods_getters', + self::PARAMETRABLE_PARAMS_SCHEMA, + null, + ], + [ + \nur\b\params\parametrable_utils::class, + '_autogen_methods_setters', + self::PARAMETRABLE_PARAMS_SCHEMA, + null, + ], + ]; + const _AUTO_GETTERS = /*autogen*/[ + 'getParseHeaders' => 'parse_headers', + 'getHeaders' => 'headers', + 'getSkipLines' => 'skip_lines', + 'getMapEmpty' => 'map_empty', + ]; + const _AUTO_SETTERS = /*autogen*/[ + 'setParseHeaders' => 'parse_headers', + 'setHeaders' => 'headers', + 'setSkipLines' => 'skip_lines', + 'setMapEmpty' => 'map_empty', + ]; + #--autogen-dynamic-- +} diff --git a/nur_src/mapper/csv/Csv2AssocMapper_command.php b/nur_src/mapper/csv/Csv2AssocMapper_command.php new file mode 100644 index 0000000..a85148f --- /dev/null +++ b/nur_src/mapper/csv/Csv2AssocMapper_command.php @@ -0,0 +1,33 @@ + "analyser un flux CSV", + "action" => [self::class, "create_command"], + "cmd_args" => [ + "usage" => self::NAME, + ["-o", "--param", "args" => "value", + "action" => [self::class, "add_params"], + "help" => "spécifier une option générique", + ], + ], + ]; + + /** @var Csv2AssocMapper */ + protected static $command; + + static function create_command() { + self::$command = mappers_command::add(new Csv2AssocMapper()); + } +} diff --git a/nur_src/mapper/csv/CsvReader.php b/nur_src/mapper/csv/CsvReader.php new file mode 100644 index 0000000..af55c4b --- /dev/null +++ b/nur_src/mapper/csv/CsvReader.php @@ -0,0 +1,212 @@ +csv2assoc = new Csv2AssocMapper(); + $this->csv2assoc->setCsvFlavour([static::SEPARATOR, static::ENCLOSURE, static::ESCAPE]); + $params = null; + if ($input !== null) $params["input"] = $input; + parent::__construct($params); + } + + function setEncodingFilter(string $from, string $to="utf-8"): void { + $this->_setEncodingFilter($from, $to); + } + + const PARAMETRABLE_PARAMS_SCHEMA = [ + "input" => [null, null, "fichier en entrée"], + ]; + + static function _get_parametrable_params_schema(): array { + return array_merge(self::PARAMETRABLE_PARAMS_SCHEMA + , encoding_utils::PARAMETRABLE_PARAMS_SCHEMA + , AbstractCsvMapper::PARAMETRABLE_PARAMS_SCHEMA + , Csv2AssocMapper::PARAMETRABLE_PARAMS_SCHEMA); + } + + protected function afterSetParametrableParams(array $modifiedKeys, ?Metadata $md=null): void { + if (!in_array("input_encoding", $modifiedKeys) + && in_array("csv_flavour", $modifiedKeys)) { + $flavourName = $this->csvFlavourName; + if ($flavourName !== null) { + $flavourName = csv_defaults::verifix_name($flavourName); + $this->ppInputEncoding = csv_defaults::get_encoding($flavourName); + $modifiedKeys[] = "input_encoding"; + } + } + $this->encodingInput__afterSetParametrableParams($modifiedKeys); + } + + protected $ppInput; + + function pp_setInput($input): void { + if ($input instanceof IReader) $this->reader = $input; + else $this->ppInput = $input; + } + + /** @var Csv2AssocMapper */ + protected $csv2assoc; + /** @var string */ + private $csvFlavourName; + function pp_setCsvFlavour($csvFlavour): void { + if (is_string($csvFlavour)) $this->csvFlavourName = $csvFlavour; + $this->csv2assoc->setCsvFlavour($csvFlavour); + } + function pp_setMultiSchema(bool $multiSchema): void { + $this->csv2assoc->setMultiSchema($multiSchema); + } + function pp_setParseHeaders(?bool $parseHeaders): void { + $this->csv2assoc->setParseHeaders($parseHeaders); + } + function pp_setHeaders(?array $headers): void { + $this->csv2assoc->setHeaders($headers); + } + function pp_setSkipLines(int $skipLines): void { + $this->csv2assoc->setSkipLines($skipLines); + } + function pp_setMapEmpty($mapEmpty=null): void { + $this->csv2assoc->setMapEmpty($mapEmpty); + } + + /** @var IReader */ + protected $reader; + + protected function setup(): void { + if ($this->reader === null) { + $this->reader = reader::with($this->ppInput); + $this->_rwAppendFilters($this->reader); + } + $this->csv2assoc->ensureSetup(); + } + + protected function teardown(): void { + $this->csv2assoc->close(); + if ($this->reader !== null) { + $this->reader->close(); + $this->reader = null; + } + } + + function producer() { + $parser = $this->csv2assoc; + [$separator, $enclosure, $escape] = $parser->getCsvFlavour(); + $reader = $this->reader; + $resource = $reader->getResource(); + if ($resource !== null) { + while (!$parser->_parseLine()) { + if (fgets($resource) === false) break; + } + while (($values = fgetcsv($resource, 0, $separator, $enclosure, $escape)) !== false) { + if (!$parser->_checkHeader($values)) { + yield $parser->_mapRow($values); + } + } + } else { + while (true) { + try { + $line = $reader->readLine(); + } catch (EOFException $e) { + break; + } + if (!$parser->_parseLine()) continue; + $values = str_getcsv($line, $separator, $enclosure, $escape); + if (!$parser->_checkHeader($values)) { + yield $parser->_mapRow($values); + } + } + } + } + + ############################################################################# + const _AUTOGEN_CONSTS = [ + "" => [self::class, "_autogen_consts", true], + ]; + const _AUTOGEN_LITERALS = /*autogen*/[ + [ + \nur\b\params\parametrable_utils::class, + '\\nur\\b\\params\\parametrable_utils::class', + ], + [self::class, 'self::class'], + [ + self::PARAMETRABLE_PARAMS_SCHEMA, + 'self::PARAMETRABLE_PARAMS_SCHEMA', + ], + ]; + const _AUTOGEN_METHODS = /*autogen*/[ + [ + \nur\b\params\parametrable_utils::class, + '_autogen_methods_getters', + self::PARAMETRABLE_PARAMS_SCHEMA, + self::class, + ], + [ + \nur\b\params\parametrable_utils::class, + '_autogen_methods_setters', + self::PARAMETRABLE_PARAMS_SCHEMA, + self::class, + ], + ]; + const _AUTO_GETTERS = /*autogen*/[ + 'getInput' => 'input', + 'getInputEncoding' => 'input_encoding', + 'getOutputEncoding' => 'output_encoding', + 'getCsvFlavour' => 'csv_flavour', + 'isMultiSchema' => 'multi_schema', + 'getParseHeaders' => 'parse_headers', + 'getHeaders' => 'headers', + 'getSkipLines' => 'skip_lines', + 'getMapEmpty' => 'map_empty', + ]; + const _AUTO_SETTERS = /*autogen*/[ + 'setInput' => 'input', + 'setInputEncoding' => 'input_encoding', + 'setOutputEncoding' => 'output_encoding', + 'setCsvFlavour' => 'csv_flavour', + 'setMultiSchema' => 'multi_schema', + 'setParseHeaders' => 'parse_headers', + 'setHeaders' => 'headers', + 'setSkipLines' => 'skip_lines', + 'setMapEmpty' => 'map_empty', + ]; + #--autogen-dynamic-- +} diff --git a/nur_src/mapper/csv/CsvReader_command.php b/nur_src/mapper/csv/CsvReader_command.php new file mode 100644 index 0000000..5bdd486 --- /dev/null +++ b/nur_src/mapper/csv/CsvReader_command.php @@ -0,0 +1,69 @@ + "configurer le fichier en entrée au format CSV", + "action" => [self::class, "create_command"], + "cmd_args" => [ + "usage" => self::NAME." [file]", + ["-e", "--input-encoding", "args" => "value", + "action" => [self::class, "set_input_encoding"], + ], + ["-s", "--skip-lines", "args" => "value", "type" => "int", + "action" => [self::class, "set_skip_lines"], + ], + ["-f", "--csv-flavour", "args" => "value", + "action" => [self::class, "set_csv_flavour"], + ], + ["-o", "--param", "args" => "value", + "action" => [self::class, "add_params"], + "help" => "spécifier une option générique", + ], + ["args" => [["file"]], + "action" => [self::class, "set_file"], + ], + ], + ]; + + const F_OPTION = ["-f", "--input", "args" => "file", + "action" => [self::class, "set_file"], + ]; + const E_OPTION = ["-e", "--input-encoding", "args" => "value", + "action" => [self::class, "set_input_encoding"], + ]; + + /** @var CsvReader */ + protected static $command; + + static function create_command() { + self::$command = new CsvReader(); + } + + static function set_file($file) { + $file = A::last(A::with($file)); + self::get()->setInput($file); + } + + static function set_input_encoding(string $input_encoding) { + self::get()->setEncodingFilter($input_encoding); + } + + static function set_skip_lines(int $skip_lines) { + self::get()->setSkipLines($skip_lines); + } + + static function set_csv_flavour(string $csv_flavour) { + self::get()->setCsvFlavour($csv_flavour); + } +} diff --git a/nur_src/mapper/csv/CsvWriter.php b/nur_src/mapper/csv/CsvWriter.php new file mode 100644 index 0000000..20a7c7d --- /dev/null +++ b/nur_src/mapper/csv/CsvWriter.php @@ -0,0 +1,195 @@ +assoc2csv = new Assoc2CsvMapper(); + $this->assoc2csv->setCsvFlavour([static::SEPARATOR, static::ENCLOSURE, static::ESCAPE]); + parent::__construct(); + $this->pp_setOutput($output); + } + + function setEncodingFilter(string $to, string $from="utf-8"): void { + $this->_setEncodingFilter($from, $to); + } + + const PARAMETRABLE_PARAMS_SCHEMA = [ + "output" => [null, null, "fichier en sortie"], + ]; + + static function _get_parametrable_params_schema(): array { + return array_merge(self::PARAMETRABLE_PARAMS_SCHEMA + , encoding_utils::PARAMETRABLE_PARAMS_SCHEMA + , AbstractCsvMapper::PARAMETRABLE_PARAMS_SCHEMA + , Assoc2CsvMapper::PARAMETRABLE_PARAMS_SCHEMA); + } + + protected function afterSetParametrableParams(array $modifiedKeys, ?Metadata $md=null): void { + if (!in_array("output_encoding", $modifiedKeys) + && in_array("csv_flavour", $modifiedKeys)) { + $flavourName = $this->csvFlavourName; + if ($flavourName !== null) { + $flavourName = csv_defaults::verifix_name($flavourName); + $this->ppOutputEncoding = csv_defaults::get_encoding($flavourName); + $modifiedKeys[] = "output_encoding"; + } + } + $this->encodingOutput__afterSetParametrableParams($modifiedKeys); + } + + /** @var Assoc2CsvMapper */ + protected $assoc2csv; + private $csvFlavourName; + function pp_setCsvFlavour($csvFlavour): void { + if (is_string($csvFlavour)) $this->csvFlavourName = $csvFlavour; + $this->assoc2csv->setCsvFlavour($csvFlavour); + } + function pp_setMultiSchema(bool $multiSchema=true): void { + $this->assoc2csv->setMultiSchema($multiSchema); + } + function pp_setHeaders(array $headers): void { + $this->assoc2csv->setHeaders($headers); + } + function pp_setOutputHeaders(bool $outputHeaders=true): void { + $this->assoc2csv->setOutputHeaders($outputHeaders); + } + + protected function setup(): void { + parent::setup(); + $this->setupWriter(true); + $this->assoc2csv->ensureSetup(); + } + + protected function teardown(): void { + $this->assoc2csv->close(); + $this->teardownWriter(true); + } + + function _consume(iterable $items): void { + $mapper = $this->assoc2csv; + [$separator, $enclosure, $escape] = $mapper->getCsvFlavour(); + $writer = $this->getWriter(); + $resource = $writer->getResource(); + if ($resource !== null) { + [$skipLine, $outputHeaders, $headers] = $mapper->_checkHeaders(null); + if ($skipLine) fwrite($resource, "\n"); + if ($outputHeaders) { + $cols = $mapper->_cookHeaders($headers); + fputcsv($resource, $cols, $separator, $enclosure, $escape); + $mapper->_setOutputHeaders($headers); + } + foreach ($items as $value) { + $row = A::with($value); + [$skipLine, $outputHeaders, $headers] = $mapper->_checkHeaders($row); + if ($skipLine) fwrite($resource, "\n"); + if ($outputHeaders) { + $cols = $mapper->_cookHeaders($headers); + fputcsv($resource, $cols, $separator, $enclosure, $escape); + $mapper->_setOutputHeaders($headers); + } + $cols = $mapper->_cookValues($headers, $row); + fputcsv($resource, $cols, $separator, $enclosure, $escape); + } + fflush($resource); + } else { + [$skipLine, $outputHeaders, $headers] = $mapper->_checkHeaders(null); + if ($skipLine) $writer->wnl(); + if ($outputHeaders) { + $cols = $mapper->_cookHeaders($headers); + $writer->write($mapper->getLine($cols, false)); + $mapper->_setOutputHeaders($headers); + } + foreach ($items as $value) { + $row = A::with($value); + [$skipLine, $outputHeaders, $headers] = $mapper->_checkHeaders($row); + if ($skipLine) $writer->wnl(); + if ($outputHeaders) { + $cols = $mapper->_cookHeaders($headers); + $writer->write($mapper->getLine($cols, false)); + $mapper->_setOutputHeaders($headers); + } + $cols = $mapper->_cookValues($headers, $row); + $writer->write($mapper->getLine($cols, false)); + } + } + } + + ############################################################################# + const _AUTOGEN_CONSTS = [ + "" => [self::class, "_autogen_consts", true], + ]; + const _AUTOGEN_LITERALS = /*autogen*/[ + [ + \nur\b\params\parametrable_utils::class, + '\\nur\\b\\params\\parametrable_utils::class', + ], + [self::class, 'self::class'], + [ + self::PARAMETRABLE_PARAMS_SCHEMA, + 'self::PARAMETRABLE_PARAMS_SCHEMA', + ], + ]; + const _AUTOGEN_METHODS = /*autogen*/[ + [ + \nur\b\params\parametrable_utils::class, + '_autogen_methods_getters', + self::PARAMETRABLE_PARAMS_SCHEMA, + self::class, + ], + [ + \nur\b\params\parametrable_utils::class, + '_autogen_methods_setters', + self::PARAMETRABLE_PARAMS_SCHEMA, + self::class, + ], + ]; + const _AUTO_GETTERS = /*autogen*/[ + 'getOutput' => 'output', + 'getInputEncoding' => 'input_encoding', + 'getOutputEncoding' => 'output_encoding', + 'getCsvFlavour' => 'csv_flavour', + 'isMultiSchema' => 'multi_schema', + 'getHeaders' => 'headers', + 'isOutputHeaders' => 'output_headers', + ]; + const _AUTO_SETTERS = /*autogen*/[ + 'setOutput' => 'output', + 'setInputEncoding' => 'input_encoding', + 'setOutputEncoding' => 'output_encoding', + 'setCsvFlavour' => 'csv_flavour', + 'setMultiSchema' => 'multi_schema', + 'setHeaders' => 'headers', + 'setOutputHeaders' => 'output_headers', + ]; + #--autogen-dynamic-- +} diff --git a/nur_src/mapper/csv/CsvWriter_command.php b/nur_src/mapper/csv/CsvWriter_command.php new file mode 100644 index 0000000..2a78991 --- /dev/null +++ b/nur_src/mapper/csv/CsvWriter_command.php @@ -0,0 +1,62 @@ + "configurer le fichier en sortie au format CSV", + "action" => [self::class, "create_command"], + "cmd_args" => [ + "usage" => self::NAME." [file]", + ["-t", "--output-encoding", "args" => "value", + "action" => [self::class, "set_output_encoding"], + ], + ["-f", "--csv-flavour", "args" => "value", + "action" => [self::class, "set_csv_flavour"], + ], + ["-o", "--param", "args" => "value", + "action" => [self::class, "add_params"], + "help" => "spécifier une option générique", + ], + ["args" => [["file"]], + "action" => [self::class, "set_file"], + ], + ], + ]; + + const O_OPTION = ["-o", "--output", "args" => "file", + "action" => [self::class, "set_file"], + ]; + const T_OPTION = ["-t", "--output-encoding", "args" => "value", + "action" => [self::class, "set_output_encoding"], + ]; + + /** @var CsvWriter */ + protected static $command; + + static function create_command() { + self::$command = new CsvWriter(); + } + + static function set_file($file) { + $file = A::last(A::with($file)); + self::get()->setOutput($file); + } + + static function set_output_encoding(string $output_encoding) { + self::get()->setEncodingFilter($output_encoding); + } + + static function set_csv_flavour(string $csv_flavour) { + self::get()->setCsvFlavour($csv_flavour); + } +} diff --git a/nur_src/mapper/csv/csv_defaults.php b/nur_src/mapper/csv/csv_defaults.php new file mode 100644 index 0000000..44018cd --- /dev/null +++ b/nur_src/mapper/csv/csv_defaults.php @@ -0,0 +1,52 @@ + self::OO_NAME, + "xl" => self::XL_NAME, + ]; + + static final function verifix_name(string $name): string { + if (array_key_exists(strtolower($name), self::NAME_MAP)) { + $name = self::NAME_MAP[strtolower($name)]; + } + return $name; + } + + private static function invalid_name(string $name): ValueException { + return new ValueException("$name: format CSV invalide"); + } + + static final function get_flavour(string $name): array { + switch ($name) { + case self::OO_NAME: return self::OO_FLAVOUR; + case self::XL_NAME: return self::XL_FLAVOUR; + default: throw self::invalid_name($name); + } + } + + static final function get_encoding(string $name): string { + switch ($name) { + case self::OO_NAME: return self::OO_ENCODING; + case self::XL_NAME: return self::XL_ENCODING; + default: throw self::invalid_name($name); + } + } +} diff --git a/nur_src/mapper/fsv/Assoc2FsvMapper.php b/nur_src/mapper/fsv/Assoc2FsvMapper.php new file mode 100644 index 0000000..13de785 --- /dev/null +++ b/nur_src/mapper/fsv/Assoc2FsvMapper.php @@ -0,0 +1,118 @@ +FSV_SCHEMA(); + $fsvSchema = new FsvSchema($fsvSchema); + $this->setFsvSchema($fsvSchema); + } + + const PARAMETRABLE_PARAMS_SCHEMA = [ + "fsv_schema" => [null, null, "schéma des données en sortie"], + ]; + + static function _get_parametrable_params_schema(): array { + return array_merge(self::PARAMETRABLE_PARAMS_SCHEMA + , encoding_utils::PARAMETRABLE_PARAMS_SCHEMA); + } + + /** @var FsvSchema */ + protected $fsvSchema; + function pp_setFsvSchema($fsvSchema): void { + if ($fsvSchema instanceof FsvSchema) $this->fsvSchema = $fsvSchema; + elseif (is_array($fsvSchema)) $this->fsvSchema->setFsvSchema($fsvSchema); + elseif ($fsvSchema !== null) throw ValueException::invalid_value($fsvSchema, "fsvSchema"); + } + function pp_setInputEncoding(?string $inputEncoding): void { + $this->fsvSchema->setDataEncoding($inputEncoding); + } + function pp_setOutputEncoding(?string $outputEncoding): void { + $this->fsvSchema->setOutputEncoding($outputEncoding); + } + + function getFsvSchema(): array { + return $this->fsvSchema->getFsvSchema(); + } + + function getFsvColumns(): array { + return $this->fsvSchema->getFsvColumns(); + } + + function _formatLine(array $row): string { + $fsvSchema = $this->fsvSchema; + if (!$fsvSchema->checkFsvSchema(false)) { + $fsvSchema->setFsvSchema($this->getOvalue(fsv_defaults::OKEY_FSV_SCHEMA)); + $fsvSchema->checkFsvSchema(); + } + return $this->fsvSchema->formatLine($row); + } + + function mapper($item) { + return $this->_formatLine(A::with($item)); + } + + ############################################################################# + const _AUTOGEN_CONSTS = [ + "" => [self::class, "_autogen_consts", true], + ]; + const _AUTOGEN_LITERALS = /*autogen*/[ + [ + \nur\b\params\parametrable_utils::class, + '\\nur\\b\\params\\parametrable_utils::class', + ], + [self::class, 'self::class'], + [ + self::PARAMETRABLE_PARAMS_SCHEMA, + 'self::PARAMETRABLE_PARAMS_SCHEMA', + ], + ]; + const _AUTOGEN_METHODS = /*autogen*/[ + [ + \nur\b\params\parametrable_utils::class, + '_autogen_methods_getters', + self::PARAMETRABLE_PARAMS_SCHEMA, + self::class, + ], + [ + \nur\b\params\parametrable_utils::class, + '_autogen_methods_setters', + self::PARAMETRABLE_PARAMS_SCHEMA, + self::class, + ], + ]; + const _AUTO_GETTERS = /*autogen*/[ + 'getFsvSchema' => 'fsv_schema', + 'getInputEncoding' => 'input_encoding', + 'getOutputEncoding' => 'output_encoding', + ]; + const _AUTO_SETTERS = /*autogen*/[ + 'setFsvSchema' => 'fsv_schema', + 'setInputEncoding' => 'input_encoding', + 'setOutputEncoding' => 'output_encoding', + ]; + #--autogen-dynamic-- +} diff --git a/nur_src/mapper/fsv/Assoc2FsvMapper_command.php b/nur_src/mapper/fsv/Assoc2FsvMapper_command.php new file mode 100644 index 0000000..42cc5e4 --- /dev/null +++ b/nur_src/mapper/fsv/Assoc2FsvMapper_command.php @@ -0,0 +1,51 @@ + "convertir en FSV", + "action" => [self::class, "create_command"], + "cmd_args" => [ + "usage" => self::NAME, + ["-r", "--run-code", "args" => "value", + "action" => [self::class, "set_schema_from_code"], + "help" => "exécuter le code et utiliser sa valeur de retour comme schéma", + ], + ["-f", "--run-file", "args" => "file", + "action" => [self::class, "set_schema_from_file"], + "help" => "exécuter le fichier et utiliser sa valeur de retour comme schéma", + ], + ["-o", "--param", "args" => "value", + "action" => [self::class, "add_params"], + "help" => "spécifier une option générique", + ], + ], + ]; + + /** @var Assoc2FsvMapper */ + protected static $command; + + static function create_command() { + self::$command = mappers_command::add(new Assoc2FsvMapper()); + } + + static function set_schema_from_code(string $code) { + $schema = eval($code); + self::get()->setFsvSchema($schema); + } + + static function set_schema_from_file(string $file) { + $schema = require($file); + self::get()->setFsvSchema($schema); + } +} diff --git a/nur_src/mapper/fsv/Fsv2AssocMapper.php b/nur_src/mapper/fsv/Fsv2AssocMapper.php new file mode 100644 index 0000000..3fd5bec --- /dev/null +++ b/nur_src/mapper/fsv/Fsv2AssocMapper.php @@ -0,0 +1,164 @@ +FSV_SCHEMA(); + $fsvSchema = new FsvSchema($fsvSchema); + $this->pp_setFsvSchema($fsvSchema); + } + + const PARAMETRABLE_PARAMS_SCHEMA = [ + "fsv_schema" => [null, null, "schéma des données en entrée"], + "map_empty" => ["?string", null, "valeur adoptée pour les chaines vides"], + "output_seq" => ["bool", null, "faut-il générer en-têtes et données en tableaux séquentiels?"], + "skip_lines" => ["int", null, "nombre de lignes à sauter en entrée"], + ]; + + static function _get_parametrable_params_schema(): array { + return array_merge(self::PARAMETRABLE_PARAMS_SCHEMA + , encoding_utils::PARAMETRABLE_PARAMS_SCHEMA); + } + + /** @var FsvSchema */ + protected $fsvSchema; + function pp_setFsvSchema($fsvSchema): void { + if ($fsvSchema instanceof FsvSchema) $this->fsvSchema = $fsvSchema; + elseif (is_array($fsvSchema)) $this->fsvSchema->setFsvSchema($fsvSchema); + elseif ($fsvSchema !== null) throw ValueException::invalid_value($fsvSchema, "fsvSchema"); + } + function pp_setInputEncoding(?string $inputEncoding): void { + $this->fsvSchema->setInputEncoding($inputEncoding); + } + function pp_setOutputEncoding(?string $outputEncoding): void { + $this->fsvSchema->setDataEncoding($outputEncoding); + } + function pp_setMapEmpty($mapEmpty=null): void { + $this->fsvSchema->setMapEmpty($mapEmpty); + } + function pp_setOutputSeq(bool $outputSeq=true): void { + $this->fsvSchema->setOutputSeq($outputSeq); + } + + function getFsvSchema(): array { + return $this->fsvSchema->getFsvSchema(); + } + + function getFsvColumns(): array { + return $this->fsvSchema->getFsvColumns(); + } + + /** @var int */ + protected $ppSkipLines = 0; + + protected function setup(): void { + parent::setup(); + $fsvSchema = $this->fsvSchema; + $fsvSchema->checkFsvSchema(); + $this->setOvalue(fsv_defaults::OKEY_FSV_SCHEMA, $fsvSchema->getFsvSchema()); + $this->setOvalue(fsv_defaults::OKEY_FSV_COLUMNS, $fsvSchema->getFsvColumns()); + } + + function _parseLine(): bool { + if ($this->ppSkipLines > 0) { + $this->ppSkipLines--; + return false; + } + return true; + } + + function _outputKeys(bool $reset=true): bool { + return $this->fsvSchema->_outputKeys($reset); + } + + function _getKeys(): array { + return $this->fsvSchema->_getKeys(); + } + + function _parseColumns(string $line): array { + return $this->fsvSchema->parseRow($line); + } + + function mapper($item) { + if (!$this->_parseLine()) return $this->mapToNone(); + + $arrays = []; + if ($this->_outputKeys()) $arrays[] = $this->_getKeys(); + if (!$this->eof) $arrays[] = $this->_parseColumns($item); + return $this->mapTo($arrays); + } + + ############################################################################# + const _AUTOGEN_CONSTS = [ + "" => [self::class, "_autogen_consts", true], + ]; + const _AUTOGEN_LITERALS = /*autogen*/[ + [ + \nur\b\params\parametrable_utils::class, + '\\nur\\b\\params\\parametrable_utils::class', + ], + [self::class, 'self::class'], + [ + self::PARAMETRABLE_PARAMS_SCHEMA, + 'self::PARAMETRABLE_PARAMS_SCHEMA', + ], + ]; + const _AUTOGEN_METHODS = /*autogen*/[ + [ + \nur\b\params\parametrable_utils::class, + '_autogen_methods_getters', + self::PARAMETRABLE_PARAMS_SCHEMA, + self::class, + ], + [ + \nur\b\params\parametrable_utils::class, + '_autogen_methods_setters', + self::PARAMETRABLE_PARAMS_SCHEMA, + self::class, + ], + ]; + const _AUTO_GETTERS = /*autogen*/[ + 'getFsvSchema' => 'fsv_schema', + 'getMapEmpty' => 'map_empty', + 'isOutputSeq' => 'output_seq', + 'getSkipLines' => 'skip_lines', + 'getInputEncoding' => 'input_encoding', + 'getOutputEncoding' => 'output_encoding', + ]; + const _AUTO_SETTERS = /*autogen*/[ + 'setFsvSchema' => 'fsv_schema', + 'setMapEmpty' => 'map_empty', + 'setOutputSeq' => 'output_seq', + 'setSkipLines' => 'skip_lines', + 'setInputEncoding' => 'input_encoding', + 'setOutputEncoding' => 'output_encoding', + ]; + #--autogen-dynamic-- +} diff --git a/nur_src/mapper/fsv/Fsv2AssocMapper_command.php b/nur_src/mapper/fsv/Fsv2AssocMapper_command.php new file mode 100644 index 0000000..faea85d --- /dev/null +++ b/nur_src/mapper/fsv/Fsv2AssocMapper_command.php @@ -0,0 +1,51 @@ + "analyser un flux FSV", + "action" => [self::class, "create_command"], + "cmd_args" => [ + "usage" => self::NAME, + ["-r", "--run-code", "args" => "value", + "action" => [self::class, "set_schema_from_code"], + "help" => "exécuter le code et utiliser sa valeur de retour comme schéma", + ], + ["-f", "--run-file", "args" => "file", + "action" => [self::class, "set_schema_from_file"], + "help" => "exécuter le fichier et utiliser sa valeur de retour comme schéma", + ], + ["-o", "--param", "args" => "value", + "action" => [self::class, "add_params"], + "help" => "spécifier une option générique", + ], + ], + ]; + + /** @var Fsv2AssocMapper */ + protected static $command; + + static function create_command() { + self::$command = mappers_command::add(new Fsv2AssocMapper()); + } + + static function set_schema_from_code(string $code) { + $schema = eval($code); + self::get()->setFsvSchema($schema); + } + + static function set_schema_from_file(string $file) { + $schema = require($file); + self::get()->setFsvSchema($schema); + } +} diff --git a/nur_src/mapper/fsv/FsvReader.php b/nur_src/mapper/fsv/FsvReader.php new file mode 100644 index 0000000..7a57a5f --- /dev/null +++ b/nur_src/mapper/fsv/FsvReader.php @@ -0,0 +1,174 @@ +fsv2assoc = new Fsv2AssocMapper(); + $this->setFsvSchema($fsvSchema); + $this->setInput($input); + } + + const PARAMETRABLE_PARAMS_SCHEMA = [ + "input" => [null, null, "fichier en entrée"], + ]; + + static function _get_parametrable_params_schema(): array { + return array_merge(self::PARAMETRABLE_PARAMS_SCHEMA + , Fsv2AssocMapper::PARAMETRABLE_PARAMS_SCHEMA + , encoding_utils::PARAMETRABLE_PARAMS_SCHEMA); + } + + protected $ppInput; + + function pp_setInput($input): void { + if ($input instanceof IReader) $this->reader = $input; + else $this->ppInput = $input; + } + + /** @var Fsv2AssocMapper */ + protected $fsv2assoc; + function pp_setFsvSchema($fsvSchema): void { + $this->fsv2assoc->setFsvSchema($fsvSchema); + } + function pp_setMapEmpty($emptyIs=null, bool $mapEmpty=true): void { + $this->fsv2assoc->setMapEmpty($emptyIs, $mapEmpty); + } + function pp_setOutputSeq(bool $outputSeq=true): void { + $this->fsv2assoc->setOutputSeq($outputSeq); + } + function pp_setSkipLines(int $skipLines): void { + $this->fsv2assoc->setSkipLines($skipLines); + } + function pp_setInputEncoding(?string $inputEncoding): void { + $this->fsv2assoc->setInputEncoding($inputEncoding); + } + function pp_setOutputEncoding(?string $outputEncoding): void { + $this->fsv2assoc->setOutputEncoding($outputEncoding); + } + + /** @var IReader */ + protected $reader; + + protected function setup(): void { + if ($this->reader === null) { + $this->reader = reader::with($this->ppInput); + $this->_rwAppendFilters($this->reader); + } + if ($this->sharedOobdManager !== null) { + $this->fsv2assoc->setSharedOobdManager($this->sharedOobdManager); + } + $this->fsv2assoc->ensureSetup(); + } + + protected function teardown(): void { + $this->fsv2assoc->close(); + if ($this->reader !== null) { + $this->reader->close(); + $this->reader = null; + } + } + + function producer() { + $parser = $this->fsv2assoc; + $reader = $this->reader; + $resource = $reader->getResource(); + if ($resource !== null) { + while (!$parser->_parseLine()) { + if (fgets($resource) === false) break; + } + if ($parser->_outputKeys()) yield $parser->_getKeys(); + while (($line = fgets($resource)) !== false) { + $line = str::strip_nl($line); + yield $parser->_parseColumns($line); + } + } else { + while (true) { + try { + $line = $reader->readLine(); + } catch (EOFException $e) { + break; + } + if (!$parser->_parseLine()) continue; + if ($parser->_outputKeys()) yield $parser->_getKeys(); + yield $parser->_parseColumns($line); + } + } + } + + ############################################################################# + const _AUTOGEN_CONSTS = [ + "" => [self::class, "_autogen_consts", true], + ]; + const _AUTOGEN_LITERALS = /*autogen*/[ + [ + \nur\b\params\parametrable_utils::class, + '\\nur\\b\\params\\parametrable_utils::class', + ], + [self::class, 'self::class'], + [ + self::PARAMETRABLE_PARAMS_SCHEMA, + 'self::PARAMETRABLE_PARAMS_SCHEMA', + ], + ]; + const _AUTOGEN_METHODS = /*autogen*/[ + [ + \nur\b\params\parametrable_utils::class, + '_autogen_methods_getters', + self::PARAMETRABLE_PARAMS_SCHEMA, + self::class, + ], + [ + \nur\b\params\parametrable_utils::class, + '_autogen_methods_setters', + self::PARAMETRABLE_PARAMS_SCHEMA, + self::class, + ], + ]; + const _AUTO_GETTERS = /*autogen*/[ + 'getInput' => 'input', + 'getFsvSchema' => 'fsv_schema', + 'getMapEmpty' => 'map_empty', + 'isOutputSeq' => 'output_seq', + 'getSkipLines' => 'skip_lines', + 'getInputEncoding' => 'input_encoding', + 'getOutputEncoding' => 'output_encoding', + ]; + const _AUTO_SETTERS = /*autogen*/[ + 'setInput' => 'input', + 'setFsvSchema' => 'fsv_schema', + 'setMapEmpty' => 'map_empty', + 'setOutputSeq' => 'output_seq', + 'setSkipLines' => 'skip_lines', + 'setInputEncoding' => 'input_encoding', + 'setOutputEncoding' => 'output_encoding', + ]; + #--autogen-dynamic-- +} diff --git a/nur_src/mapper/fsv/FsvReader_command.php b/nur_src/mapper/fsv/FsvReader_command.php new file mode 100644 index 0000000..932f5cc --- /dev/null +++ b/nur_src/mapper/fsv/FsvReader_command.php @@ -0,0 +1,80 @@ + "configurer le fichier en entrée au format FSV", + "action" => [self::class, "create_command"], + "cmd_args" => [ + "usage" => self::NAME." [file]", + ["-r", "--run-code", "args" => "value", + "action" => [self::class, "set_schema_from_code"], + "help" => "exécuter le code et utiliser sa valeur de retour comme schéma", + ], + ["-f", "--run-file", "args" => "file", + "action" => [self::class, "set_schema_from_file"], + "help" => "exécuter le fichier et utiliser sa valeur de retour comme schéma", + ], + ["-e", "--input-encoding", "args" => "value", + "action" => [self::class, "set_input_encoding"], + ], + ["-s", "--skip-lines", "args" => "value", "type" => "int", + "action" => [self::class, "set_skip_lines"], + ], + ["-o", "--param", "args" => "value", + "action" => [self::class, "add_params"], + "help" => "spécifier une option générique", + ], + ["args" => [["file"]], + "action" => [self::class, "set_file"], + ], + ], + ]; + + const F_OPTION = ["-f", "--input", "args" => "file", + "action" => [self::class, "set_file"], + ]; + const E_OPTION = ["-e", "--input-encoding", "args" => "value", + "action" => [self::class, "set_input_encoding"], + ]; + + /** @var FsvReader */ + protected static $command; + + static function create_command() { + self::$command = new FsvReader(); + } + + static function set_schema_from_code(string $code) { + $schema = eval($code); + self::get()->setFsvSchema($schema); + } + + static function set_schema_from_file(string $file) { + $schema = require($file); + self::get()->setFsvSchema($schema); + } + + static function set_file($file) { + $file = A::last(A::with($file)); + self::get()->setInput($file); + } + + static function set_input_encoding(string $input_encoding) { + self::get()->setInputEncoding($input_encoding); + } + + static function set_skip_lines(int $skip_lines) { + self::get()->setSkipLines($skip_lines); + } +} diff --git a/nur_src/mapper/fsv/FsvSchema.php b/nur_src/mapper/fsv/FsvSchema.php new file mode 100644 index 0000000..ac19bf2 --- /dev/null +++ b/nur_src/mapper/fsv/FsvSchema.php @@ -0,0 +1,385 @@ + ["string", null, "nom du champ", "required" => true], + "size" => ["int", null, "taille du champ", "required" => true], + "type" => ["?string", null, "type du champ: string, number, date"], + "precision" => ["?int", null, "précision pour un champ de type number"], + "format" => ["?string", null, "format à appliquer avant écriture"], + # ces champs sont en principe calculés automatiquement: + "position" => ["?int", null, "position du premier caractère du champ (commence à 1)"], + "index" => ["?int", null, "index du premier caractère du champ (commence à 1)"], + ]; + + /** @var Metadata */ + private static $column_md; + private static function column_md(): Metadata { + return md_utils::ensure_md(self::$column_md, self::COLUMN_SCHEMA); + } + + protected function INPUT_ENCODING(): ?string { + return static::INPUT_ENCODING; + } const INPUT_ENCODING = "latin1"; + + protected function DATA_ENCODING(): ?string { + return static::DATA_ENCODING; + } const DATA_ENCODING = "utf-8"; + + protected function OUTPUT_ENCODING(): ?string { + return static::OUTPUT_ENCODING; + } const OUTPUT_ENCODING = "latin1//TRANSLIT//IGNORE"; + + protected function FSV_SCHEMA(): ?array { + return self::FSV_SCHEMA; + } const FSV_SCHEMA = null; + + function __construct(?array $fsvSchema=null) { + $this->setFsvSchema($fsvSchema); + $this->setInputEncoding(null); + $this->setDataEncoding(null); + $this->setOutputEncoding(null); + } + + protected $inputEncoding; + + function setInputEncoding(?string $inputEncoding): self { + if ($inputEncoding === null) $inputEncoding = $this->INPUT_ENCODING(); + $this->inputEncoding = $inputEncoding; + return $this; + } + + protected $dataEncoding; + + function setDataEncoding(?string $dataEncoding): self { + if ($dataEncoding === null) $dataEncoding = $this->DATA_ENCODING(); + $this->dataEncoding = $dataEncoding; + return $this; + } + + protected $outputEncoding; + + function setOutputEncoding(?string $outputEncoding): self { + if ($outputEncoding === null) $outputEncoding = $this->OUTPUT_ENCODING(); + $this->outputEncoding = $outputEncoding; + return $this; + } + + protected function iconvInput(array $row): array { + $inputEncoding = $this->inputEncoding; + $dataEncoding = $this->dataEncoding; + if ($inputEncoding !== null && $dataEncoding !== null) { + foreach ($row as &$col) { + if (is_string($col)) $col = iconv($inputEncoding, $dataEncoding, $col); + }; unset($col); + } + return $row; + } + + protected function iconvOutput(string $line): string { + $dataEncoding = $this->dataEncoding; + $outputEncoding = $this->outputEncoding; + if ($outputEncoding !== null && $dataEncoding !== null) { + $line = iconv($dataEncoding, $outputEncoding, $line); + } + return $line; + } + + /** @var bool */ + protected $shouldMapEmpty = false; + + protected $mapEmpty = null; + + function setMapEmpty($mapEmpty=null): void { + $this->shouldMapEmpty = true; + $this->mapEmpty = $mapEmpty; + } + + /** + * @var bool faut-il générer les en-têtes et les données sous forme de + * tableaux séquentiels? + */ + protected $outputSeq; + + /** @var bool faut-il afficher les en-têtes en sortie? */ + protected $outputKeys; + + function setOutputSeq(bool $outputSeq=true): void { + $this->outputSeq = $outputSeq; + $this->outputKeys = true; + } + + /** @var array */ + protected $fsvSchema; + + function getFsvSchema(): array { + return $this->fsvSchema; + } + + /** @var array liste des noms des champs */ + protected $fsvColumns; + + function getFsvColumns(): array { + return $this->fsvColumns; + } + + function setFsvSchema(?array $fsvSchema): self { + if ($fsvSchema === null) $fsvSchema = $this->FSV_SCHEMA(); + if ($fsvSchema === null) return $this; + $column_md = self::column_md(); + $index = 0; + $position = 1; + $columns = []; + foreach ($fsvSchema as $key => &$sfield) { + if ($key === $index) { + # séquentiel + $index++; + $column_md->ensureSchema($sfield); + } else { + # associatif + $column_md->ensureSchema($sfield, $key); + } + A::replace_n($sfield, "type", "string"); + A::replace_n($sfield, "precision", 0); + A::replace_n($sfield, "position", $position); + A::replace_n($sfield, "index", $position - 1); + $this->validateSfield($sfield); + $position += $sfield["size"]; + $columns[] = $sfield["name"]; + }; unset($sfield); + $this->fsvSchema = $fsvSchema; + $this->fsvColumns = $columns; + return $this; + } + + protected function validateSfield($sfield): void { + $type = $sfield["type"]; + switch ($type) { + case "string": + case "number": + break; + case "date": + $size = $sfield["size"]; + #XXX tenir compte du format + if ($size != 6 && $size != 8) { + throw new ValueException("date type require size=6 or size=8"); + } + break; + default: + throw ValueException::invalid_value($type, "type"); + } + } + + function checkFsvSchema(bool $throw=true): bool { + if ($this->fsvSchema !== null) return true; + elseif ($throw) throw new ValueException("a schema is required"); + else return false; + } + + ############################################################################# + + function _outputKeys(bool $reset=true): bool { + $outputKeys = $this->outputSeq && $this->outputKeys; + if ($reset) $this->outputKeys = false; + return $outputKeys; + } + + function _getKeys(): array { + return $this->fsvColumns; + } + + function parseRow(string $line, bool $iconv=true): array { + $this->checkFsvSchema(); + $outputSeq = $this->outputSeq; + $row = []; + $length = strlen($line); + foreach ($this->fsvSchema as $sfield) { + [ + "name" => $name, + "index" => $index, + "size" => $size, + "type" => $type, + ] = $sfield; + if ($index >= $length) { + if ($outputSeq) $row[] = false; + else $row[$name] = false; + } else { + $value = substr($line, $index, $size); + $blank = str_pad("", $size); + switch ($type) { + case "string": + $value = $this->parseString($value, $sfield); + break; + case "number": + if ($value === $blank) $value = false; + else $value = $this->parseNumber($value, $sfield); + break; + case "date": + if ($value === $blank) $value = false; + else $value = $this->parseDate($value, $sfield); + break; + default: + throw IllegalAccessException::unexpected_state(); + } + if ($value === "" && $this->shouldMapEmpty) $value = $this->mapEmpty; + if ($outputSeq) $row[] = $value; + else $row[$name] = $value; + } + } + if ($iconv) $row = $this->iconvInput($row); + return $row; + } + + protected function parseString(string $value, array $sfield): string { + return rtrim($value); + } + + protected function parseNumber(string $value, array $sfield) { + $precision = $sfield["precision"]; + if ($precision == 0) { + $value = intval($value); + } else { + $value = doubleval($value) / (10**$precision); + } + return $value; + } + + protected function parseDate(string $value, array $sfield) { + $size = $sfield["size"]; + $dd = substr($value, 0, 2); + $mm = substr($value, 2, 2); + if ($size == 8) { + $yyyy = substr($value, 4, 4); + } elseif ($size == 6) { + $yy = substr($value, 4, 2); + $yyyy = Date::fix_any_year($yy); + } else { + throw IllegalAccessException::unexpected_state(); + } + return "$dd/$mm/$yyyy"; + } + + ############################################################################# + + protected static function ensure_size(string $value, int $size): string { + $length = mb_strlen($value); + if ($length < $size) { + while ($length < $size) { + $value .= " "; + $length = mb_strlen($value); + } + } elseif ($length > $size) { + $value = mb_substr($value, 0, $size); + } + return $value; + } + + protected static function invalid_size(string $value, int $actual_size, array $sfield): ValueException { + ["name" => $name, "size" => $size, "format" => $format] = $sfield; + return new ValueException("field=$name with format $format, value=|$value|, expected size=$size, actual size=$actual_size"); + } + + function formatLine(array $row, bool $iconv=true): string { + $this->checkFsvSchema(); + $line = []; + foreach ($this->fsvSchema as $sfield) { + [ + "name" => $name, + "size" => $size, + "type" => $type, + ] = $sfield; + $value = A::get($row, $name, ""); + if ($value === false) { + $value = self::ensure_size("", $size); + } else { + switch ($type) { + case "string": + $value = $this->formatString($value, $sfield); + break; + case "number": + $value = $this->formatNumber($value, $sfield); + break; + case "date": + $value = $this->formatDate($value, $sfield); + break; + default: + throw IllegalAccessException::unexpected_state(); + } + } + $line[] = $value; + } + $line = implode("", $line); + if ($iconv) $line = $this->iconvOutput($line); + return $line; + } + + function formatString($value, $sfield): string { + ["size" => $size, "format" => $format] = $sfield; + $value = strval($value); + if ($format !== null) { + $func = [$this, "stringFormat_$format"]; + $value = func::call($func, $value, $sfield); + $actualSize = mb_strlen($value); + if ($actualSize != $size) { + throw self::invalid_size($value, $actualSize, $sfield); + } + } else { + $value = self::ensure_size($value, $size); + } + return $value; + } + + function stringFormat_upper(string $value): string { + return mb_strtoupper($value); + } + + function stringFormat_lower(string $value): string { + return mb_strtolower($value); + } + + function formatNumber($value, $sfield): string { + ["size" => $size, "precision" => $precision, "format" => $format] = $sfield; + if ($format !== null) { + $value = sprintf($format, $value); + $actualSize = strlen($value); + if ($actualSize != $size) { + throw self::invalid_size($value, $actualSize, $sfield); + } + } elseif ($precision == 0) { + $value = sprintf("%0${size}u", $value); + } else { + $size++; + $value = sprintf("%0${size}.${precision}F", $value); + $value = str_replace(".", "", $value); + } + return $value; + } + + function formatDate($value, $sfield): string { + $date = new Date($value); + ["size" => $size, "format" => $format] = $sfield; + if ($format !== null) { + $value = $date->format($format); + $actualSize = strlen($value); + if ($actualSize != $size) { + throw self::invalid_size($value, $actualSize, $sfield); + } + } elseif ($size == 6) { + $value = $date->format("dmy"); + } elseif ($size == 8) { + $value = $date->format("dmY"); + } else { + throw IllegalAccessException::unexpected_state(); + } + return $value; + } +} diff --git a/nur_src/mapper/fsv/FsvWriter.php b/nur_src/mapper/fsv/FsvWriter.php new file mode 100644 index 0000000..8994233 --- /dev/null +++ b/nur_src/mapper/fsv/FsvWriter.php @@ -0,0 +1,114 @@ +assoc2fsv = new Assoc2FsvMapper(); + $this->pp_setOutput($output); + } + + const PARAMETRABLE_PARAMS_SCHEMA = [ + "output" => [null, null, "fichier en sortie"], + ]; + + static function _get_parametrable_params_schema(): array { + return array_merge(self::PARAMETRABLE_PARAMS_SCHEMA + , Assoc2FsvMapper::PARAMETRABLE_PARAMS_SCHEMA + , encoding_utils::PARAMETRABLE_PARAMS_SCHEMA); + } + + /** @var Assoc2FsvMapper */ + protected $assoc2fsv; + function pp_setFsvSchema($fsvSchema): void { + $this->assoc2fsv->setFsvSchema($fsvSchema); + } + function pp_setInputEncoding(?string $inputEncoding): void { + $this->assoc2fsv->setInputEncoding($inputEncoding); + } + function pp_setOutputEncoding(?string $outputEncoding): void { + $this->assoc2fsv->setOutputEncoding($outputEncoding); + } + + protected function setup(): void { + parent::setup(); + $this->setupWriter(false); + $this->assoc2fsv->ensureSetup(); + } + + protected function teardown(): void { + $this->assoc2fsv->close(); + $this->teardownWriter(false); + } + + function _consume(iterable $items): void { + $mapper = $this->assoc2fsv; + $mapper->setSharedOobdManager($this->sharedOobdManager); + $writer = $this->getWriter(); + foreach ($items as $value) { + $writer->wnl($mapper->_formatLine($value)); + } + } + + ############################################################################# + const _AUTOGEN_CONSTS = [ + "" => [self::class, "_autogen_consts", true], + ]; + const _AUTOGEN_LITERALS = /*autogen*/[ + [ + \nur\b\params\parametrable_utils::class, + '\\nur\\b\\params\\parametrable_utils::class', + ], + [self::class, 'self::class'], + [ + self::PARAMETRABLE_PARAMS_SCHEMA, + 'self::PARAMETRABLE_PARAMS_SCHEMA', + ], + ]; + const _AUTOGEN_METHODS = /*autogen*/[ + [ + \nur\b\params\parametrable_utils::class, + '_autogen_methods_getters', + self::PARAMETRABLE_PARAMS_SCHEMA, + self::class, + ], + [ + \nur\b\params\parametrable_utils::class, + '_autogen_methods_setters', + self::PARAMETRABLE_PARAMS_SCHEMA, + self::class, + ], + ]; + const _AUTO_GETTERS = /*autogen*/[ + 'getOutput' => 'output', + 'getFsvSchema' => 'fsv_schema', + 'getInputEncoding' => 'input_encoding', + 'getOutputEncoding' => 'output_encoding', + ]; + const _AUTO_SETTERS = /*autogen*/[ + 'setOutput' => 'output', + 'setFsvSchema' => 'fsv_schema', + 'setInputEncoding' => 'input_encoding', + 'setOutputEncoding' => 'output_encoding', + ]; + #--autogen-dynamic-- +} diff --git a/nur_src/mapper/fsv/FsvWriter_command.php b/nur_src/mapper/fsv/FsvWriter_command.php new file mode 100644 index 0000000..c1049fb --- /dev/null +++ b/nur_src/mapper/fsv/FsvWriter_command.php @@ -0,0 +1,73 @@ + "configurer le fichier en sortie au format FSV", + "action" => [self::class, "create_command"], + "cmd_args" => [ + "usage" => self::NAME." [file]", + ["-r", "--run-code", "args" => "value", + "action" => [self::class, "set_schema_from_code"], + "help" => "exécuter le code et utiliser sa valeur de retour comme schéma", + ], + ["-f", "--run-file", "args" => "file", + "action" => [self::class, "set_schema_from_file"], + "help" => "exécuter le fichier et utiliser sa valeur de retour comme schéma", + ], + ["-t", "--output-encoding", "args" => "value", + "action" => [self::class, "set_output_encoding"], + ], + ["-o", "--param", "args" => "value", + "action" => [self::class, "add_params"], + "help" => "spécifier une option générique", + ], + ["args" => [["file"]], + "action" => [self::class, "set_file"], + ], + ], + ]; + + const O_OPTION = ["-o", "--output", "args" => "file", + "action" => [self::class, "set_file"], + ]; + const T_OPTION = ["-t", "--output-encoding", "args" => "value", + "action" => [self::class, "set_output_encoding"], + ]; + + /** @var FsvWriter */ + protected static $command; + + static function create_command() { + self::$command = new FsvWriter(); + } + + static function set_schema_from_code(string $code) { + $schema = eval($code); + self::get()->setFsvSchema($schema); + } + + static function set_schema_from_file(string $file) { + $schema = require($file); + self::get()->setFsvSchema($schema); + } + + static function set_file($file) { + $file = A::last(A::with($file)); + self::get()->setOutput($file); + } + + static function set_output_encoding(string $output_encoding) { + self::get()->setEncodingFilter($output_encoding); + } +} diff --git a/nur_src/mapper/fsv/fsv_defaults.php b/nur_src/mapper/fsv/fsv_defaults.php new file mode 100644 index 0000000..be56246 --- /dev/null +++ b/nur_src/mapper/fsv/fsv_defaults.php @@ -0,0 +1,7 @@ +actions[] = [self::ACTION_SET_VALUE, $key, $value]; + } elseif (mapper_utils::check_prefix($action, $value, "addp:", "+#")) { + $this->actions[] = [self::ACTION_ADD_PREFIX, $key, $value]; + } elseif (mapper_utils::check_prefix($action, $value, "adds:", "+%", "+")) { + $this->actions[] = [self::ACTION_ADD_SUFFIX, $key, $value]; + } elseif (mapper_utils::check_prefix($action, $value, "delp:", "-#", "#")) { + $this->actions[] = [self::ACTION_DEL_PREFIX, $key, $value]; + } elseif (mapper_utils::check_prefix($action, $value, "dels:", "-%", "%", "-")) { + $this->actions[] = [self::ACTION_DEL_SUFFIX, $key, $value]; + } elseif (mapper_utils::check_prefix($action, $value, "left:", "l:")) { + $length = intval($value); + $this->actions[] = [self::ACTION_LEFT, $key, $length]; + } elseif (mapper_utils::check_prefix($action, $value, "right:", "r:")) { + $length = intval($value); + $this->actions[] = [self::ACTION_RIGHT, $key, $length]; + } elseif (mapper_utils::check_prefix($action, $value, "substr:", "mid:", "m:")) { + preg_match('/^([^:]*)(?::(.*))?$/', $value, $vs); + $offset = $vs[1]; + $offset = $offset? intval($offset): 0; + $length = A::get($vs, 2); + if ($length !== null) $length = intval($length); + $this->actions[] = [self::ACTION_SUBSTR, $key, $offset, $length]; + } elseif (mapper_utils::check_prefix($action, $value, "replace:", "repl:", "r//", "r/")) { + if (str::_starts_with("r//", $action)) { + $sep = "/"; + $limit = -1; + } elseif (str::_starts_with("r/", $action)) { + $sep = "/"; + $limit = 1; + } else { + $sep = ":"; + $limit = -1; + } + [$from, $to] = str::split_pair($value, $sep); + if ($sep == "/") str::del_suffix($to, "/"); + $this->actions[] = [self::ACTION_REPLACE, $key, false, $from, $to, $limit]; + } elseif (mapper_utils::check_prefix($action, $value, "split:", "|", "split")) { + if ($action === "split") $value = ","; + $this->actions[] = [self::ACTION_SPLIT, $key, $value]; + } elseif (mapper_utils::check_prefix($action, $value, "join:", "§", "join")) { + if ($action === "join") $value = ","; + $this->actions[] = [self::ACTION_JOIN, $key, $value]; + } elseif (mapper_utils::check_prefix($action, $value, "trunc:")) { + preg_match('/^([^:]*)(?::([^:]*)(?::([^:]*))?)?$/', $value, $vs); + $length = $vs[1]; + $length = $length? intval($length): 0; + $ellips = base::t2(A::get($vs, 2)); + $suffix = A::get($vs, 3); + $this->actions[] = [self::ACTION_TRUNC, $key, $length, $ellips, $suffix]; + } elseif (mapper_utils::check_prefix($action, $value, "trim", "t")) { + $this->actions[] = [self::ACTION_TRIM, $key]; + } elseif (mapper_utils::check_prefix($action, $value, "ltrim", "lt")) { + $this->actions[] = [self::ACTION_LTRIM, $key]; + } elseif (mapper_utils::check_prefix($action, $value, "rtrim", "rt")) { + $this->actions[] = [self::ACTION_RTRIM, $key]; + } elseif (mapper_utils::check_prefix($action, $value, "lower", "l")) { + $this->actions[] = [self::ACTION_LOWER, $key]; + } elseif (mapper_utils::check_prefix($action, $value, "lower1", "l1")) { + $this->actions[] = [self::ACTION_LOWER1, $key]; + } elseif (mapper_utils::check_prefix($action, $value, "upper", "u")) { + $this->actions[] = [self::ACTION_UPPER, $key]; + } elseif (mapper_utils::check_prefix($action, $value, "upper1", "u1")) { + $this->actions[] = [self::ACTION_UPPER1, $key]; + } elseif (mapper_utils::check_prefix($action, $value, "upperw", "uw")) { + $this->actions[] = [self::ACTION_UPPERW, $key]; + } elseif (mapper_utils::check_prefix($action, $value, "split_all:", "split_all")) { + if ($action === "split_all") $value = ","; + $this->actions[] = [self::ACTION_SPLIT_ALL, $key, $value]; + } elseif (mapper_utils::check_prefix($action, $value, "join_all:", "join_all")) { + if ($action === "join_all") $value = ","; + $this->actions[] = [self::ACTION_JOIN_ALL, $key, $value]; + } else { + # par défaut, spécifier la valeur + $this->actions[] = [self::ACTION_SET_VALUE, $key, $action]; + } + return $this; + } + + function addActionSetValue(?string $value, ?string $key=null): self { + $this->actions[] = [self::ACTION_SET_VALUE, $key, $value]; + return $this; + } + + function addActionAddPrefix(?string $value, ?string $key=null): self { + $this->actions[] = [self::ACTION_ADD_PREFIX, $key, $value]; + return $this; + } + + function addActionAddSuffix(?string $value, ?string $key=null): self { + $this->actions[] = [self::ACTION_ADD_SUFFIX, $key, $value]; + return $this; + } + + function addActionDelPrefix(?string $value, ?string $key=null): self { + $this->actions[] = [self::ACTION_DEL_PREFIX, $key, $value]; + return $this; + } + + function addActionDelSuffix(?string $value, ?string $key=null): self { + $this->actions[] = [self::ACTION_DEL_SUFFIX, $key, $value]; + return $this; + } + + function addActionLeft(int $length, ?string $key=null): self { + $this->actions[] = [self::ACTION_LEFT, $key, $length]; + return $this; + } + + function addActionRight(int $length, ?string $key=null): self { + $this->actions[] = [self::ACTION_RIGHT, $key, $length]; + return $this; + } + + function addActionSubstr(int $offset, ?int $length, ?string $key=null): self { + $this->actions[] = [self::ACTION_SUBSTR, $key, $offset, $length]; + return $this; + } + + function addActionReplace(string $from, ?string $to, int $limit=-1, ?string $key=null): self { + $this->actions[] = [self::ACTION_REPLACE, $key, false, $from, $to, $limit]; + return $this; + } + + function addActionSplit(?string $sep, ?string $key=null): self { + $this->actions[] = [self::ACTION_SPLIT, $key, $sep]; + return $this; + } + + function addActionJoin(?string $sep, ?string $key=null): self { + $this->actions[] = [self::ACTION_JOIN, $key, $sep]; + return $this; + } + + function addActionTrunc(int $length, bool $ellips=false, ?string $suffix=null, ?string $key=null): self { + $this->actions[] = [self::ACTION_TRUNC, $key, $length, $ellips, $suffix]; + return $this; + } + + function addActionTrim(?string $key=null): self { + $this->actions[] = [self::ACTION_TRIM, $key]; + return $this; + } + + function addActionLtrim(?string $key=null): self { + $this->actions[] = [self::ACTION_LTRIM, $key]; + return $this; + } + + function addActionRtrim(?string $key=null): self { + $this->actions[] = [self::ACTION_RTRIM, $key]; + return $this; + } + + function addActionLower(?string $key=null): self { + $this->actions[] = [self::ACTION_LOWER, $key]; + return $this; + } + + function addActionLower1(?string $key=null): self { + $this->actions[] = [self::ACTION_LOWER1, $key]; + return $this; + } + + function addActionUpper(?string $key=null): self { + $this->actions[] = [self::ACTION_UPPER, $key]; + return $this; + } + + function addActionUpper1(?string $key=null): self { + $this->actions[] = [self::ACTION_UPPER1, $key]; + return $this; + } + + function addActionUpperw(?string $key=null): self { + $this->actions[] = [self::ACTION_UPPERW, $key]; + return $this; + } + + const PARAMETRABLE_PARAMS_SCHEMA = [ + "actions" => ["?array", null, "actions à effectuer sur les chaines du flux"], + "marked_only" => ["bool", null, "ne faire les actions que sur les éléments marqués"], + ]; + + function pp_setActions(?array $actions): void { + if ($actions !== null) { + foreach ($actions as $action) { + $this->addAction($action); + } + } + } + + /** @var bool */ + protected $markedOnly; + + ############################################################################# + + protected static function apply(&$item, ?string $forKey, callable $func, array $args): void { + if (is_array($item)) { + foreach ($item as $key => &$value) { + if ($forKey !== null & $key != $forKey) continue; + self::apply($value, null, $func, $args); + } + } else { + $item = $func($item, ...$args); + } + } + + protected static function set_value($value) { + if (base::z($value)) return $value; + return strval($value); + } + + protected static function add_prefix($value, ?string $prefix) { + return "$prefix$value"; + } + + protected static function add_suffix($value, ?string $suffix) { + return "$value$suffix"; + } + + protected static function split($value, string $sep): array { + if ($value === null) return []; + return explode($sep, strval($value)); + } + + protected function doActionItem(&$item, int $action, ?string $forKey, array $params): void { + switch ($action) { + case self::ACTION_SET_VALUE: + if (is_array($item)) { + foreach ($item as $key => &$value) { + if ($forKey !== null & $key != $forKey) continue; + $value = self::set_value(...$params); + } + } else { + $item = self::set_value(...$params); + } + break; + case self::ACTION_ADD_PREFIX: + self::apply($item, $forKey, [static::class, "add_prefix"], $params); + break; + case self::ACTION_ADD_SUFFIX: + self::apply($item, $forKey, [static::class, "add_suffix"], $params); + break; + case self::ACTION_DEL_PREFIX: + self::apply($item, $forKey, [static::class, "del_prefix"], $params); + break; + case self::ACTION_DEL_SUFFIX: + self::apply($item, $forKey, [static::class, "del_suffix"], $params); + break; + case self::ACTION_LEFT: + self::apply($item, $forKey, [static::class, "left"], $params); + break; + case self::ACTION_RIGHT: + self::apply($item, $forKey, [static::class, "right"], $params); + break; + case self::ACTION_SUBSTR: + self::apply($item, $forKey, [static::class, "substr"], $params); + break; + case self::ACTION_REPLACE: + $regexp = $params[0]; + $params = array_slice($params, 1); + if ($regexp) { + #XXX à implémenter + #self::apply($item, $forKey, [static::class, "preg_replace"], $params); + } else { + self::apply($item, $forKey, [static::class, "str_replace"], $params); + } + break; + case self::ACTION_SPLIT: + self::apply($item, $forKey, [static::class, "split"], $params); + break; + case self::ACTION_JOIN: + self::apply($item, $forKey, [static::class, "join"], $params); + break; + case self::ACTION_TRUNC: + self::apply($item, $forKey, [static::class, "trunc"], $params); + break; + case self::ACTION_TRIM: + self::apply($item, $forKey, [static::class, "trim"], $params); + break; + case self::ACTION_LTRIM: + self::apply($item, $forKey, [static::class, "ltrim"], $params); + break; + case self::ACTION_RTRIM: + self::apply($item, $forKey, [static::class, "rtrim"], $params); + break; + case self::ACTION_LOWER: + self::apply($item, $forKey, [static::class, "lower"], $params); + break; + case self::ACTION_LOWER1: + self::apply($item, $forKey, [static::class, "lower1"], $params); + break; + case self::ACTION_UPPER: + self::apply($item, $forKey, [static::class, "upper"], $params); + break; + case self::ACTION_UPPER1: + self::apply($item, $forKey, [static::class, "upper1"], $params); + break; + case self::ACTION_UPPERW: + self::apply($item, $forKey, [static::class, "upperw"], $params); + break; + default: + throw IllegalAccessException::not_implemented("action $action"); + } + } + + protected static function split_all($item, string $sep): array { + if (base::z($item)) return $item; + if (is_array($item)) $item = self::join_all($item, ""); + return explode($sep, strval($item)); + } + + protected static function join_all($item, string $sep): string { + if (base::z($item)) return $item; + return implode($sep, A::with($item)); + } + + protected function doActionWhole(&$item, int $action, ?string $forKey, array $params): void { + switch ($action) { + case self::ACTION_SPLIT_ALL: + $item = static::split_all($item, ...$params); + break; + case self::ACTION_JOIN_ALL: + $item = static::join_all($item, ...$params); + break; + default: + throw IllegalAccessException::not_implemented("action $action"); + } + } + + protected function setup(): void { + parent::setup(); + if ($this->markedOnly === null) { + $this->markedOnly = mark_utils::is_use_marks($this); + } + } + + function mapper($item) { + if (!$this->markedOnly || $this->isItemMarked($item)) { + foreach ($this->actions as $actionParams) { + [$action, $forKey] = array_slice($actionParams, 0, 2); + $params = array_slice($actionParams, 2); + if ($action < self::ACTION_WHOLE_ITEM) { + $this->doActionItem($item, $action, $forKey, $params); + } else { + $this->doActionWhole($item, $action, $forKey, $params); + } + } + } + return $item; + } +} diff --git a/nur_src/mapper/item/Assoc2SeqMapper.php b/nur_src/mapper/item/Assoc2SeqMapper.php new file mode 100644 index 0000000..df3f414 --- /dev/null +++ b/nur_src/mapper/item/Assoc2SeqMapper.php @@ -0,0 +1,147 @@ + ["bool", false, "les flux multi-schémas sont-ils supportés?"], + "keys" => ["array", null, "liste et ordre des champs en sortie"], + "output_keys" => ["bool", true, "faut-il afficher les en-têtes en sortie?"], + ]; + + /** @var bool les flux avec plusieurs schémas sont-ils supportés? */ + protected $ppMultiSchema; + + /** + * @var array liste et ordre des champs en sortie. si cette valeur n'est pas + * spécifiée, elle est calculée à partir du premier élément du flux. + */ + protected $ppKeys; + + /** @var bool faut-il afficher les en-têtes en sortie? */ + protected $ppOutputKeys; + + private static final function is_different(array $h1, array $h2): bool { + sort($h1); + sort($h2); + return $h1 != $h2; + } + + private function computeKeys(array $item) { + return array_keys($item); + } + + function _checkKeys(?array $item): array { + $skipLine = false; + if ($item === null) { + $outputKeys = $this->ppOutputKeys && $this->ppKeys !== null; + return [$skipLine, $outputKeys, $this->ppKeys]; + } + $prevKeys = $this->ppKeys; + if ($this->ppMultiSchema) { + # vérifier si le schéma a changé + $keys = $this->computeKeys($item); + if ($prevKeys === null) $prevKeys = $keys; + if (self::is_different($prevKeys, $keys)) { + $skipLine = true; + $this->ppOutputKeys = true; + } else { + $keys = $prevKeys; + } + } else { + $keys = $prevKeys; + if ($keys === null) $keys = $this->computeKeys($item); + } + return [$skipLine, $this->ppOutputKeys, $keys]; + } + + function _setOutputKeys(array $keys): void { + $this->ppKeys = $keys; + $this->ppOutputKeys = false; + } + + function _cookValues(array $keys, array $item): array { + $values = []; + foreach ($keys as $header) { + $values[] = A::get($item, $header, false); + } + return $values; + } + + function mapper($item) { + if ($this->eof) $item = null; + else $item = A::with($item); + $arrays = []; + + [$skipLine, $outputKeys, $keys] = $this->_checkKeys($item); + if ($skipLine) $arrays[] = []; + if ($outputKeys) { + $arrays[] = $keys; + $this->_setOutputKeys($keys); + } + if ($item !== null) { + $arrays[] = $this->_cookValues($keys, $item); + } + + return $this->mapTo($arrays); + } + + ############################################################################# + const _AUTOGEN_CONSTS = [ + "" => [self::class, "_autogen_consts"], + ]; + const _AUTOGEN_LITERALS = /*autogen*/[ + [ + \nur\b\params\parametrable_utils::class, + '\\nur\\b\\params\\parametrable_utils::class', + ], + [ + self::PARAMETRABLE_PARAMS_SCHEMA, + 'self::PARAMETRABLE_PARAMS_SCHEMA', + ], + ]; + const _AUTOGEN_METHODS = /*autogen*/[ + [ + \nur\b\params\parametrable_utils::class, + '_autogen_methods_getters', + self::PARAMETRABLE_PARAMS_SCHEMA, + null, + ], + [ + \nur\b\params\parametrable_utils::class, + '_autogen_methods_setters', + self::PARAMETRABLE_PARAMS_SCHEMA, + null, + ], + ]; + const _AUTO_GETTERS = /*autogen*/[ + 'isMultiSchema' => 'multi_schema', + 'getKeys' => 'keys', + 'isOutputKeys' => 'output_keys', + ]; + const _AUTO_SETTERS = /*autogen*/[ + 'setMultiSchema' => 'multi_schema', + 'setKeys' => 'keys', + 'setOutputKeys' => 'output_keys', + ]; + #--autogen-dynamic-- +} diff --git a/nur_src/mapper/item/Assoc2SeqMapper_command.php b/nur_src/mapper/item/Assoc2SeqMapper_command.php new file mode 100644 index 0000000..f99544f --- /dev/null +++ b/nur_src/mapper/item/Assoc2SeqMapper_command.php @@ -0,0 +1,33 @@ + "convertir en tableaux séquentiels", + "action" => [self::class, "create_command"], + "cmd_args" => [ + "usage" => self::NAME, + ["-o", "--param", "args" => "value", + "action" => [self::class, "add_params"], + "help" => "spécifier une option générique", + ], + ], + ]; + + /** @var Assoc2SeqMapper */ + protected static $command; + + static function create_command() { + self::$command = mappers_command::add(new Assoc2SeqMapper()); + } +} diff --git a/nur_src/mapper/item/AttributeFilterMapper.php b/nur_src/mapper/item/AttributeFilterMapper.php new file mode 100644 index 0000000..6c58096 --- /dev/null +++ b/nur_src/mapper/item/AttributeFilterMapper.php @@ -0,0 +1,112 @@ +pp_setFields($fields); + } + + const PARAMETRABLE_PARAMS_SCHEMA = [ + "fields" => ["?array", null, "liste des champs à sélectionner"], + "add_fields" => ["?array", null, "liste des champs à ajouter"], + "remove_fields" => ["?array", null, "liste des champs à supprimer"], + ]; + + /** @var array */ + protected $ppFields; + + function pp_setFields(?array $fields): void { + if ($fields !== null) $fields = A::flattened($fields); + $this->ppFields = $fields; + } + + /** @var array */ + protected $ppAddFields; + + function pp_setAddFields(?array $addFields): void { + if ($addFields !== null) $addFields = A::flattened($addFields); + $this->ppAddFields = $addFields; + } + + /** @var array */ + protected $ppRemoveFields; + + function pp_setRemoveFields(?array $removeFields): void { + if ($removeFields !== null) $removeFields = A::flattened($removeFields); + $this->ppRemoveFields = $removeFields; + } + + function mapper($item) { + $item = A::with($item); + $fields = $this->ppFields; + $addFields = $this->ppAddFields; + $removeFields = $this->ppRemoveFields; + if ($fields === null && ($addFields !== null || $removeFields !== null)) { + $fields = array_keys($item); + } + if ($addFields !== null) A::merge($fields, $addFields); + $item = A::select_default($item, $fields); + if ($removeFields !== null) { + foreach ($removeFields as $field) { + unset($item[$field]); + } + } + return $item; + } + + ############################################################################# + const _AUTOGEN_CONSTS = [ + "" => [self::class, "_autogen_consts"], + ]; + const _AUTOGEN_LITERALS = /*autogen*/[ + [ + \nur\b\params\parametrable_utils::class, + '\\nur\\b\\params\\parametrable_utils::class', + ], + [ + self::PARAMETRABLE_PARAMS_SCHEMA, + 'self::PARAMETRABLE_PARAMS_SCHEMA', + ], + ]; + const _AUTOGEN_METHODS = /*autogen*/[ + [ + \nur\b\params\parametrable_utils::class, + '_autogen_methods_getters', + self::PARAMETRABLE_PARAMS_SCHEMA, + null, + ], + [ + \nur\b\params\parametrable_utils::class, + '_autogen_methods_setters', + self::PARAMETRABLE_PARAMS_SCHEMA, + null, + ], + ]; + const _AUTO_GETTERS = /*autogen*/[ + 'getFields' => 'fields', + 'getAddFields' => 'add_fields', + 'getRemoveFields' => 'remove_fields', + ]; + const _AUTO_SETTERS = /*autogen*/[ + 'setFields' => 'fields', + 'setAddFields' => 'add_fields', + 'setRemoveFields' => 'remove_fields', + ]; + #--autogen-dynamic-- +} diff --git a/nur_src/mapper/item/AttributeFilterMapper_command.php b/nur_src/mapper/item/AttributeFilterMapper_command.php new file mode 100644 index 0000000..fe62f67 --- /dev/null +++ b/nur_src/mapper/item/AttributeFilterMapper_command.php @@ -0,0 +1,79 @@ + "filtrer et mapper des attributs", + "action" => [self::class, "create_command"], + "cmd_args" => [ + "usage" => self::NAME." [name[=default]...]", + ["-a", "--add-fields", "args" => ["value", [null]], + "action" => [self::class, "set_add_fields"], + ], + ["-r", "--remove-fields", "args" => ["value", [null]], + "action" => [self::class, "set_remove_fields"], + ], + ["-o", "--param", "args" => "value", + "action" => [self::class, "add_params"], + "help" => "spécifier une option générique", + ], + ["args" => [["value", null]], + "action" => [self::class, "set_fields"], + ], + ], + ]; + + /** @var AttributeFilterMapper */ + protected static $command; + + static function create_command() { + self::$command = mappers_command::add(new AttributeFilterMapper()); + } + + static function set_fields(array $names) { + $fields = []; + foreach ($names as $name) { + if (mapper_utils::split_param($name, $name, $type, $value)) { + if ($type !== null) $value = types::with($type, $value); + $fields[$name] = $value; + } else { + $fields[] = $name; + } + } + if ($fields) self::$command->setFields($fields); + } + + static function set_add_fields(array $names) { + $fields = []; + foreach ($names as $name) { + if (mapper_utils::split_param($name, $name, $type, $value)) { + if ($type !== null) $value = types::with($type, $value); + $fields[$name] = $value; + } else { + $fields[] = $name; + } + } + self::$command->setAddFields($fields); + } + + static function set_remove_fields(array $names) { + $fields = []; + foreach ($names as $name) { + mapper_utils::split_param($name, $name, $type, $value); + $fields[] = $name; + } + self::$command->setRemoveFields($fields); + } +} diff --git a/nur_src/mapper/item/EnsureLatin1Mapper.php b/nur_src/mapper/item/EnsureLatin1Mapper.php new file mode 100644 index 0000000..694a37e --- /dev/null +++ b/nur_src/mapper/item/EnsureLatin1Mapper.php @@ -0,0 +1,27 @@ + "appliquer un mapper générique", + ], function(string $command): ?array { + if (!is_subclass_of($command, Mapper::class)) return null; + return [$command, "help" => "appliquer le mapper $command", + "action" => [static::class, "create_command"], + "cmd_args" => [ + "usage" => $command, + ["-o", "--param", "args" => "value", + "action" => [static::class, "add_params"], + "help" => "spécifier une option générique", + ], + ], + ]; + }]; + } + + /** @var Mapper */ + protected static $command; + + static function create_command($value, $name, $arg) { + self::$command = mappers_command::add(new $arg()); + } +} diff --git a/nur_src/mapper/item/ItemFilterMapper.php b/nur_src/mapper/item/ItemFilterMapper.php new file mode 100644 index 0000000..231c4f0 --- /dev/null +++ b/nur_src/mapper/item/ItemFilterMapper.php @@ -0,0 +1,400 @@ + ["key", self::ACTION_KEEP, "action à effectuer si l'élément correspondant"], + "skip_items" => ["int", null, "nombre d'éléments à ignorer au début du flux"], + "max_count" => ["int", null, "nombre d'éléments maximum à retourner"], + "conditions" => ["?array", null, "conditions que doivent remplir l'élément pour être sélectionné"], + ]; + + /** @var int */ + protected $ppAction; + + function pp_setAction($action): void { + switch ($action) { + case self::ACTION_FILTER: + case self::ACTION_KEEP: + case self::ACTION_MARK: + break; + case "filter": + case "x": + $action = self::ACTION_FILTER; + break; + case "keep": + case "k": + $action = self::ACTION_KEEP; + break; + case "mark": + case "m": + $action = self::ACTION_MARK; + break; + default: + throw ValueException::invalid_value($action, "action"); + } + if ($action == self::ACTION_MARK) mark_utils::set_use_marks($this); + $this->ppAction = $action; + } + + function setActionFilter(): self { + $this->pp_setAction(self::ACTION_FILTER); + return $this; + } + + function setActionKeep(): self { + $this->pp_setAction(self::ACTION_KEEP); + return $this; + } + + function setActionMark(): self { + $this->pp_setAction(self::ACTION_MARK); + return $this; + } + + /** @var int nombre d'éléments à ignorer au début du flux */ + protected $ppSkipItems = 0; + + /** @var int nombre d'éléments maximum à retourner */ + protected $ppMaxCount = -1; + + /** @var array */ + protected $ppConditions = []; + + function pp_setConditions(?array $conditions): void { + if ($conditions !== null) { + foreach ($conditions as $condition) { + $this->addCondition($condition); + } + } + } + + function addCondition(?string $condition): self { + if (preg_match('/^([A-Za-z0-9_]*)=(.*)$/', $condition, $vs)) { + $key = A::get($vs, 1); + $condition = A::get($vs, 2); + } else { + $key = null; + } + if (mapper_utils::check_prefix($condition, $value, "eq:", "=")) { + $this->ppConditions[] = [self::CONDITION_EQ, $key, $value]; + } elseif (mapper_utils::check_prefix($condition, $value, "ne:", "!=")) { + $this->ppConditions[] = [self::CONDITION_NE, $key, $value]; + } elseif (mapper_utils::check_prefix($condition, $value, "lt:", "<")) { + $this->ppConditions[] = [self::CONDITION_LT, $key, $value]; + } elseif (mapper_utils::check_prefix($condition, $value, "le:", "<=")) { + $this->ppConditions[] = [self::CONDITION_LE, $key, $value]; + } elseif (mapper_utils::check_prefix($condition, $value, "gt:", ">")) { + $this->ppConditions[] = [self::CONDITION_GT, $key, $value]; + } elseif (mapper_utils::check_prefix($condition, $value, "ge:", ">=")) { + $this->ppConditions[] = [self::CONDITION_GE, $key, $value]; + } elseif (mapper_utils::check_prefix($condition, $value, "nz")) { + $this->ppConditions[] = [self::CONDITION_NZ, $key]; + } elseif (mapper_utils::check_prefix($condition, $value, "z")) { + $this->ppConditions[] = [self::CONDITION_Z, $key]; + } elseif (mapper_utils::check_prefix($condition, $value, "notnull", "nn")) { + $this->ppConditions[] = [self::CONDITION_NN, $key]; + } elseif (mapper_utils::check_prefix($condition, $value, "null", "n")) { + $this->ppConditions[] = [self::CONDITION_N, $key]; + } elseif (mapper_utils::check_prefix($condition, $value, "true", "t")) { + $this->ppConditions[] = [self::CONDITION_TRUE, $key]; + } elseif (mapper_utils::check_prefix($condition, $value, "false", "f")) { + $this->ppConditions[] = [self::CONDITION_FALSE, $key]; + } else { + # par défaut, tester l'égalité + $this->ppConditions[] = [self::CONDITION_EQ, $key, $condition]; + } + return $this; + } + + function addConditionEq($value, ?string $key=null): self { + $this->ppConditions[] = [self::CONDITION_EQ, $key, $value]; + return $this; + } + + function addConditionNe($value, ?string $key=null): self { + $this->ppConditions[] = [self::CONDITION_NE, $key, $value]; + return $this; + } + + function addConditionLt($value, ?string $key=null): self { + $this->ppConditions[] = [self::CONDITION_LT, $key, $value]; + return $this; + } + + function addConditionLe($value, ?string $key=null): self { + $this->ppConditions[] = [self::CONDITION_LE, $key, $value]; + return $this; + } + + function addConditionGt($value, ?string $key=null): self { + $this->ppConditions[] = [self::CONDITION_GT, $key, $value]; + return $this; + } + + function addConditionGe($value, ?string $key=null): self { + $this->ppConditions[] = [self::CONDITION_GE, $key, $value]; + return $this; + } + + function addConditionZ(?string $key=null): self { + $this->ppConditions[] = [self::CONDITION_Z, $key]; + return $this; + } + + function addConditionNz(?string $key=null): self { + $this->ppConditions[] = [self::CONDITION_NZ, $key]; + return $this; + } + + function addConditionN(?string $key=null): self { + $this->ppConditions[] = [self::CONDITION_N, $key]; + return $this; + } + + function addConditionNn(?string $key=null): self { + $this->ppConditions[] = [self::CONDITION_NN, $key]; + return $this; + } + + function addConditionTrue(?string $key=null): self { + $this->ppConditions[] = [self::CONDITION_TRUE, $key]; + return $this; + } + + function addConditionFalse(?string $key=null): self { + $this->ppConditions[] = [self::CONDITION_FALSE, $key]; + return $this; + } + + ############################################################################# + + protected static function check(&$item, ?string $forKey, callable $func, array $args): bool { + if (is_array($item)) { + [$forKey, $subKey] = str::split_pair($forKey, "."); + $result = false; + foreach ($item as $key => &$value) { + if ($forKey !== null & $key != $forKey) continue; + $result = $result || self::check($value, $subKey, $func, $args); + } + return $result; + } else { + return boolval($func($item, ...$args)); + } + } + + protected static function check_eq($value, $other): bool { + return $value == $other; + } + + protected static function check_ne($value, $other): bool { + return $value != $other; + } + + protected static function check_lt($value, $other): bool { + return $value < $other; + } + + protected static function check_le($value, $other): bool { + return $value <= $other; + } + + protected static function check_gt($value, $other): bool { + return $value > $other; + } + + protected static function check_ge($value, $other): bool { + return $value >= $other; + } + + protected static function check_z($value): bool { + return $value === null || $value === false; + } + + protected static function check_nz($value): bool { + return $value !== null && $value !== false; + } + + protected static function check_n($value): bool { + return $value === null; + } + + protected static function check_nn($value): bool { + return $value !== null; + } + + protected static function check_true($value): bool { + return $value === true; + } + + protected static function check_false($value): bool { + return $value === false; + } + + /** @var int */ + private $skipped; + + /** @var int */ + private $count; + + protected function setup(): void { + parent::setup(); + $this->skipped = 0; + $this->count = 0; + } + + protected function select($item, bool $selected, ?int $action=null) { + if ($action === null) $action = $this->ppAction; + switch ($action) { + case self::ACTION_FILTER: + if ($selected) return $this->mapToNone(); + else return $item; + case self::ACTION_KEEP: + if ($selected) return $item; + else return $this->mapToNone(); + case self::ACTION_MARK: + if ($selected) $this->setItemMarked($item); + return $item; + } + throw IllegalAccessException::not_implemented("action $this->ppAction"); + } + + function mapper($item) { + if ($this->skipped < $this->ppSkipItems) { + $this->skipped++; + return $this->select($item, false, self::ACTION_KEEP); + } + if ($this->ppMaxCount < 0 || $this->count < $this->ppMaxCount) { + $this->count++; + $selected = true; + foreach ($this->ppConditions as $conditionParams) { + [$condition, $forKey] = array_slice($conditionParams, 0, 2); + $params = array_slice($conditionParams, 2); + switch ($condition) { + case self::CONDITION_EQ: + $selected = $selected && self::check($item, $forKey, [self::class, "check_eq"], $params); + break; + case self::CONDITION_NE: + $selected = $selected && self::check($item, $forKey, [self::class, "check_ne"], $params); + break; + case self::CONDITION_LT: + $selected = $selected && self::check($item, $forKey, [self::class, "check_lt"], $params); + break; + case self::CONDITION_LE: + $selected = $selected && self::check($item, $forKey, [self::class, "check_le"], $params); + break; + case self::CONDITION_GT: + $selected = $selected && self::check($item, $forKey, [self::class, "check_gt"], $params); + break; + case self::CONDITION_GE: + $selected = $selected && self::check($item, $forKey, [self::class, "check_ge"], $params); + break; + case self::CONDITION_Z: + $selected = $selected && self::check($item, $forKey, [self::class, "check_z"], $params); + break; + case self::CONDITION_NZ: + $selected = $selected && self::check($item, $forKey, [self::class, "check_nz"], $params); + break; + case self::CONDITION_N: + $selected = $selected && self::check($item, $forKey, [self::class, "check_n"], $params); + break; + case self::CONDITION_NN: + $selected = $selected && self::check($item, $forKey, [self::class, "check_nn"], $params); + break; + case self::CONDITION_TRUE: + $selected = $selected && self::check($item, $forKey, [self::class, "check_true"], $params); + break; + case self::CONDITION_FALSE: + $selected = $selected && self::check($item, $forKey, [self::class, "check_false"], $params); + break; + default: + throw IllegalAccessException::not_implemented("condition $condition"); + } + } + return $this->select($item, $selected); + } + return $this->select($item, false, self::ACTION_KEEP); + } + + ############################################################################# + const _AUTOGEN_CONSTS = [ + "" => [self::class, "_autogen_consts"], + ]; + const _AUTOGEN_LITERALS = /*autogen*/[ + [ + \nur\b\params\parametrable_utils::class, + '\\nur\\b\\params\\parametrable_utils::class', + ], + [ + self::PARAMETRABLE_PARAMS_SCHEMA, + 'self::PARAMETRABLE_PARAMS_SCHEMA', + ], + ]; + const _AUTOGEN_METHODS = /*autogen*/[ + [ + \nur\b\params\parametrable_utils::class, + '_autogen_methods_getters', + self::PARAMETRABLE_PARAMS_SCHEMA, + null, + ], + [ + \nur\b\params\parametrable_utils::class, + '_autogen_methods_setters', + self::PARAMETRABLE_PARAMS_SCHEMA, + null, + ], + ]; + const _AUTO_GETTERS = /*autogen*/[ + 'getAction' => 'action', + 'getSkipItems' => 'skip_items', + 'getMaxCount' => 'max_count', + 'getConditions' => 'conditions', + ]; + const _AUTO_SETTERS = /*autogen*/[ + 'setAction' => 'action', + 'setSkipItems' => 'skip_items', + 'setMaxCount' => 'max_count', + 'setConditions' => 'conditions', + ]; + #--autogen-dynamic-- +} diff --git a/nur_src/mapper/item/ItemFilterMapper_command.php b/nur_src/mapper/item/ItemFilterMapper_command.php new file mode 100644 index 0000000..b7efabf --- /dev/null +++ b/nur_src/mapper/item/ItemFilterMapper_command.php @@ -0,0 +1,75 @@ + "filtrer des éléments", + "action" => [self::class, "create_command"], + "cmd_args" => [ + "usage" => self::NAME, + ["group", + ["-a", "--action", "args" => "value", + "action" => [self::class, "set_action"], + ], + ["-X", "--action-filter", "value" => "filter", + "action" => [self::class, "set_action"], + ], + ["-K", "--action-keep", "value" => "keep", + "action" => [self::class, "set_action"], + ], + ["-M", "--action-mark", "value" => "mark", + "action" => [self::class, "set_action"], + ], + ], + ["-s", "--skip-items", "args" => "value", "type" => "int", + "action" => [self::class, "set_skip_items"], + ], + ["-m", "--max-count", "args" => "value", "type" => "int", + "action" => [self::class, "set_max_count"], + ], + ["-o", "--param", "args" => "value", + "action" => [self::class, "add_params"], + "help" => "spécifier une option générique", + ], + ["args" => [["value", null]], + "action" => [self::class, "add_conditions"], + ], + ], + ]; + + /** @var ItemFilterMapper */ + protected static $command; + + static function create_command() { + self::$command = mappers_command::add(new ItemFilterMapper()); + } + + static function set_action(string $action) { + self::get()->setAction($action); + } + + static function set_skip_items(int $skip_items) { + self::get()->setSkipItems($skip_items); + } + + static function set_max_count(int $max_count) { + self::get()->setMaxCount($max_count); + } + + static function add_conditions(array $conditions) { + $command = self::get(); + foreach ($conditions as $condition) { + $command->addCondition($condition); + } + } +} diff --git a/nur_src/mapper/item/LoggerConsumer.php b/nur_src/mapper/item/LoggerConsumer.php new file mode 100644 index 0000000..5411029 --- /dev/null +++ b/nur_src/mapper/item/LoggerConsumer.php @@ -0,0 +1,36 @@ + $part) { + $parts[] = "$key => ".self::to_string($part); + } + return "{ ".implode(", ", $parts)." }"; + } elseif (A::is_seq($item)) { + $parts = []; + foreach ($item as $key => $part) { + $parts[] = self::to_string($part); + } + return implode(", ", $parts); + } elseif (is_string($item)) { + return $item; + } else { + return var_export($item, true); + } + } + + function cook($item) { + msg::info(self::to_string($item)); + } +} diff --git a/nur_src/mapper/item/NumberMapper.php b/nur_src/mapper/item/NumberMapper.php new file mode 100644 index 0000000..6953414 --- /dev/null +++ b/nur_src/mapper/item/NumberMapper.php @@ -0,0 +1,10 @@ + "faire des opérations sur des nombres", + "action" => [self::class, "create_command"], + "cmd_args" => [ + "usage" => self::NAME, + ["-o", "--param", "args" => "value", + "action" => [self::class, "add_params"], + "help" => "spécifier une option générique", + ], + ], + ]; + + /** @var NumberMapper */ + protected static $command; + + static function create_command() { + self::$command = mappers_command::add(new NumberMapper()); + } +} diff --git a/nur_src/mapper/item/SchemaMapper.php b/nur_src/mapper/item/SchemaMapper.php new file mode 100644 index 0000000..3f5efba --- /dev/null +++ b/nur_src/mapper/item/SchemaMapper.php @@ -0,0 +1,260 @@ +pp_setSchema($schema); + } + + const MULTIPLEX_RESULTS_COLS = "cols"; + const MULTIPLEX_RESULTS_ROWS = "rows"; + + const PARAMETRABLE_PARAMS_SCHEMA = [ + "schema" => ["?array", null, "schéma des données"], + "use_key" => ["bool", false, "faut-il tenir compte des clés fournies par la source"], + "ensure_known_types" => ["bool", false, "faut-il vérifier et convertir les types connus?"], + "recursive" => ["bool", true, "faut-il appliquer les schémas de façon récursive"], + "ensure_types" => ["bool", true, "faut-il vérifier et convertir les types connus?"], + "throw" => ["bool", false, "faut-il lancer une exception si certains champs sont invalides?"], + "keep_results" => ["bool", false, "faut-il garder le résultat des analyses et l'insérer dans le flux?"], + "multiplex_results" => [["string"], null, "faut-il multiplexer champs et résultats d'analyse", + "allowed_values" => [self::MULTIPLEX_RESULTS_COLS, self::MULTIPLEX_RESULTS_ROWS], + ], + "check_required" => ["bool", false, "faut-il vérifier que les champs requis soient présents"], + ]; + + /** @var Metadata */ + protected $md; + + function pp_setSchema(?array $schema): self { + if ($schema === null) $schema = $this->SCHEMA(); + if ($schema !== null) $this->md = new Metadata($schema); + return $this; + } + + /** @var bool */ + protected $ppUseKey; + + /** @var bool */ + protected $ppEnsureKnownTypes; + + /** @var bool */ + protected $ppRecursive; + + /** @var bool */ + protected $ppEnsureTypes; + + /** @var bool */ + protected $ppThrow; + + /** @var bool */ + protected $ppKeepResults; + + /** @var string|null */ + protected $ppMultiplexResults; + + function pp_setMultiplexResults(string $multiplexResults): self { + switch ($multiplexResults) { + case "cols": + case "col": + case "c": + $multiplexResults = self::MULTIPLEX_RESULTS_COLS; + break; + case "rows": + case "row": + case "r": + $multiplexResults = self::MULTIPLEX_RESULTS_ROWS; + break; + default: + throw ValueException::unexpected_value($multiplexResults, [ + self::MULTIPLEX_RESULTS_COLS, + self::MULTIPLEX_RESULTS_ROWS, + ]); + } + $this->ppMultiplexResults = $multiplexResults; + return $this; + } + + function setMultiplexResults(string $multiplexResults=self::MULTIPLEX_RESULTS_COLS): void { + $this->pp_setMultiplexResults($multiplexResults); + } + + /** @var bool */ + protected $ppCheckRequired; + + const OKEY_NB_ERRORS_TOTAL = "nb_errors_total"; + + function getNbErrorsTotal(): int { + return $this->getOvalue(self::OKEY_NB_ERRORS_TOTAL); + } + + protected function setup(): void { + $this->setOvalue(self::OKEY_NB_ERRORS_TOTAL, 0); + } + + function mapper($item, $key=null) { + if (!$this->ppUseKey) $key = null; + $md = $this->md; + $md->ensureSchema($item, $key, ["recursive" => $this->ppRecursive]); + $itemResults = null; + if ($this->ppEnsureTypes) { + $md->verifix($item, $results, $this->ppThrow, $this->ppRecursive); + $nbErrors = 0; + foreach ($results as $result) { + if (!$result["valid"]) { + $nbErrors++; + $this->incOvalue(self::OKEY_NB_ERRORS_TOTAL); + } + } + if ($this->ppKeepResults) { + $item = [ + "item" => $item, + "results" => $results, + "nb_errors" => $nbErrors, + ]; + } elseif ($this->ppMultiplexResults !== null) { + switch ($this->ppMultiplexResults) { + case "cols": + $mitem = ["[nb_errors]" => $nbErrors]; + foreach ($item as $key => $value) { + $mitem[$key] = $value; + $result = A::get($results, $key); + if ($result !== null) { + $result = $result["valid"]? null: $result["error"]; + } + $mitem["[${key}_error]"] = $result; + } + foreach ($md->getCikeys() as $key) { + $result = A::get($results, $key); + if ($result !== null) { + $result = $result["valid"]? null: $result["error"]; + } + $mitem["[${key}_error]"] = $result; + } + $item = $mitem; + break; + case "rows": + # construire la ligne avec les résultats + $mitem = ["[nb_errors]" => $nbErrors]; + foreach ($item as $key => $value) { + $result = A::get($results, $key); + if ($result !== null) { + $result = $result["valid"]? null: $result["error"]; + } + $mitem[$key] = $result; + } + foreach ($md->getCikeys() as $key) { + $result = A::get($results, $key); + if ($result !== null) { + $result = $result["valid"]? null: $result["error"]; + } + $mitem[$key] = $result; + } + $itemResults = $mitem; + # puis ajouter la colonne manquante dans l'objet original + $mitem = ["[nb_errors]" => $nbErrors]; + foreach ($item as $key => $value) { + $mitem[$key] = $value; + } + foreach ($md->getCikeys() as $key) { + $mitem[$key] = null; + } + $item = $mitem; + break; + default: + throw IllegalAccessException::unexpected_state(); + } + } + } elseif ($this->ppCheckRequired) { + $md->checkRequired($item); + } + if ($itemResults === null) return $item; + else return $this->mapTo([$item, $itemResults]); + } + + ############################################################################# + const _AUTOGEN_CONSTS = [ + "" => [self::class, "_autogen_consts"], + ]; + const _AUTOGEN_LITERALS = /*autogen*/[ + [ + \nur\b\params\parametrable_utils::class, + '\\nur\\b\\params\\parametrable_utils::class', + ], + [ + self::PARAMETRABLE_PARAMS_SCHEMA, + 'self::PARAMETRABLE_PARAMS_SCHEMA', + ], + ]; + const _AUTOGEN_METHODS = /*autogen*/[ + [ + \nur\b\params\parametrable_utils::class, + '_autogen_methods_getters', + self::PARAMETRABLE_PARAMS_SCHEMA, + null, + ], + [ + \nur\b\params\parametrable_utils::class, + '_autogen_methods_setters', + self::PARAMETRABLE_PARAMS_SCHEMA, + null, + ], + ]; + const _AUTO_GETTERS = /*autogen*/[ + 'getSchema' => 'schema', + 'isUseKey' => 'use_key', + 'isEnsureKnownTypes' => 'ensure_known_types', + 'isRecursive' => 'recursive', + 'isEnsureTypes' => 'ensure_types', + 'isThrow' => 'throw', + 'isKeepResults' => 'keep_results', + 'getMultiplexResults' => 'multiplex_results', + 'isCheckRequired' => 'check_required', + ]; + const _AUTO_SETTERS = /*autogen*/[ + 'setSchema' => 'schema', + 'setUseKey' => 'use_key', + 'setEnsureKnownTypes' => 'ensure_known_types', + 'setRecursive' => 'recursive', + 'setEnsureTypes' => 'ensure_types', + 'setThrow' => 'throw', + 'setKeepResults' => 'keep_results', + 'setMultiplexResults' => 'multiplex_results', + 'setCheckRequired' => 'check_required', + ]; + #--autogen-dynamic-- +} diff --git a/nur_src/mapper/item/SchemaMapper_command.php b/nur_src/mapper/item/SchemaMapper_command.php new file mode 100644 index 0000000..82781ea --- /dev/null +++ b/nur_src/mapper/item/SchemaMapper_command.php @@ -0,0 +1,51 @@ + "appliquer un schéma", + "action" => [self::class, "create_command"], + "cmd_args" => [ + "usage" => self::NAME, + ["-r", "--run-code", "args" => "value", + "action" => [self::class, "set_schema_from_code"], + "help" => "exécuter le code et utiliser sa valeur de retour comme schéma", + ], + ["-f", "--run-file", "args" => "file", + "action" => [self::class, "set_schema_from_file"], + "help" => "exécuter le fichier et utiliser sa valeur de retour comme schéma", + ], + ["-o", "--param", "args" => "value", + "action" => [self::class, "add_params"], + "help" => "spécifier une option générique", + ], + ], + ]; + + /** @var SchemaMapper */ + protected static $command; + + static function create_command() { + self::$command = mappers_command::add(new SchemaMapper()); + } + + static function set_schema_from_code(string $code) { + $schema = eval($code); + self::get()->setFsvSchema($schema); + } + + static function set_schema_from_file(string $file) { + $schema = require($file); + self::get()->setFsvSchema($schema); + } +} diff --git a/nur_src/mapper/item/Seq2AssocMapper.php b/nur_src/mapper/item/Seq2AssocMapper.php new file mode 100644 index 0000000..30843f1 --- /dev/null +++ b/nur_src/mapper/item/Seq2AssocMapper.php @@ -0,0 +1,109 @@ + ["?bool", null, "faut-il analyser le premier élément du flux pour calculer la liste des clés en entrée?"], + "keys" => ["?array", null, "liste et ordre des clés en entrée"], + ]; + + /** + * @var ?bool faut-il analyser le premier élément du flux pour calculer la + * liste des clés en entrée? null signifie que la valeur est dynamique: elle + * vaut ($keys === null) + * + * Si ce champ est vrai, le premier élément est toujours consommé. cependant, + * la liste des champs n'est analysée que si elle n'a pas été spécifiée au + * préalable avec {@link setKeys()} + */ + protected $ppParseKeys; + + /** + * @var array liste des champs en entrée dans l'ordre. si cette valeur n'est + * pas spécifiée, elle est calculée à partir du premier élément du flux. + */ + protected $ppKeys; + + function _checkKeys(array $keys): bool { + $parseKeys = $this->ppParseKeys; + $setParseKeys = $parseKeys !== null; + if ($parseKeys === null) $parseKeys = $this->ppKeys === null; + if ($parseKeys) { + if ($this->ppKeys === null) $this->ppKeys = $keys; + if ($setParseKeys) $this->ppParseKeys = false; + return true; + } + return false; + } + + function _mapValues(array $values): array { + $keys = $this->ppKeys; + if ($keys === null) return $values; + $row = []; + $index = 0; + foreach ($keys as $key) { + $row[$key] = A::get($values, $index++, false); + } + return $row; + } + + function mapper($item) { + $values = A::with($item); + if ($this->_checkKeys($values)) return $this->mapToNone(); + return $this->_mapValues($values); + } + + ############################################################################# + const _AUTOGEN_CONSTS = [ + "" => [self::class, "_autogen_consts"], + ]; + const _AUTOGEN_LITERALS = /*autogen*/[ + [ + \nur\b\params\parametrable_utils::class, + '\\nur\\b\\params\\parametrable_utils::class', + ], + [ + self::PARAMETRABLE_PARAMS_SCHEMA, + 'self::PARAMETRABLE_PARAMS_SCHEMA', + ], + ]; + const _AUTOGEN_METHODS = /*autogen*/[ + [ + \nur\b\params\parametrable_utils::class, + '_autogen_methods_getters', + self::PARAMETRABLE_PARAMS_SCHEMA, + null, + ], + [ + \nur\b\params\parametrable_utils::class, + '_autogen_methods_setters', + self::PARAMETRABLE_PARAMS_SCHEMA, + null, + ], + ]; + const _AUTO_GETTERS = /*autogen*/[ + 'getParseKeys' => 'parse_keys', + 'getKeys' => 'keys', + ]; + const _AUTO_SETTERS = /*autogen*/[ + 'setParseKeys' => 'parse_keys', + 'setKeys' => 'keys', + ]; + #--autogen-dynamic-- +} diff --git a/nur_src/mapper/item/Seq2AssocMapper_command.php b/nur_src/mapper/item/Seq2AssocMapper_command.php new file mode 100644 index 0000000..846449e --- /dev/null +++ b/nur_src/mapper/item/Seq2AssocMapper_command.php @@ -0,0 +1,49 @@ + "convertir en tableaux associatifs", + "action" => [self::class, "create_command"], + "cmd_args" => [ + "usage" => self::NAME, + ["-k", "--parse-keys", "type" => "bool", + "action" => [self::class, "set_parse_keys", true], + ], + ["-o", "--param", "args" => "value", + "action" => [self::class, "add_params"], + "help" => "spécifier une option générique", + ], + ["args" => [["value", null]], "type" => "array", + "action" => [self::class, "set_keys"], + ], + ], + ]; + + /** @var Seq2AssocMapper */ + protected static $command; + + static function create_command() { + self::$command = mappers_command::add(new Seq2AssocMapper()); + } + + static function set_parse_keys(bool $parseKeys) { + self::get()->setParseKeys($parseKeys); + } + + static function set_keys(array $args) { + A::merge($keys, ...$args); + if ($keys) self::get()->setKeys($keys); + } +} diff --git a/nur_src/mapper/item/StreamMapper.php b/nur_src/mapper/item/StreamMapper.php new file mode 100644 index 0000000..65e7cec --- /dev/null +++ b/nur_src/mapper/item/StreamMapper.php @@ -0,0 +1,145 @@ +capacitor = $capacitor; + return $this; + } + + protected function capacitor(): Capacitor { + if ($this->capacitor === null) $this->capacitor = new Capacitor(); + return $this->capacitor; + } + + const ACTION_SPLIT = "split"; + const ACTION_JOIN = "join"; + + const PARAMETRABLE_PARAMS_SCHEMA = [ + "action" => ["string", null, "action à effectuer sur le flux"], + "max_count" => ["int", null, "nombre d'élément par batch"], + ]; + + /** @var string */ + protected $ppAction; + + /** @var int */ + protected $ppMaxCount = 0; + + function pp_setAction(?string $action, ?int $maxCount=null): void { + switch ($action) { + case "split": + case "s": + $this->ppAction = self::ACTION_SPLIT; + break; + case "join": + case "j": + $this->ppAction = self::ACTION_JOIN; + $this->count = 0; + break; + default: + if ($action !== null) { + throw ValueException::invalid_value($action, "action"); + } + } + if ($maxCount !== null) $this->ppMaxCount = $maxCount; + } + + function setActionSplit(): self { + $this->pp_setAction("split"); + return $this; + } + + function setActionJoin(?int $maxCount=null): self { + $this->pp_setAction("join", $maxCount); + return $this; + } + + function pp_setMaxCount(int $maxCount): self { + $this->pp_setAction(null, $maxCount); + return $this; + } + + protected $count; + + function mapper($item) { + switch ($this->ppAction) { + case self::ACTION_SPLIT: + if (!is_iterable($item)) $item = A::with($item); + return $this->mapTo($item); + case self::ACTION_JOIN: + if (!$this->eof) $this->capacitor()->charge($item); + $this->count++; + $discharge = $this->ppMaxCount > 0 && $this->count >= $this->ppMaxCount; + if ($discharge || $this->eof) { + $values = $this->capacitor()->discharge(); + $this->count = 0; + if ($values instanceof Traversable) $values = iterator_to_array($values); + $item = $values? [$values]: null; + } else { + $item = null; + } + return $this->mapTo($item); + } + return $item; + } + + ############################################################################# + const _AUTOGEN_CONSTS = [ + "" => [self::class, "_autogen_consts"], + ]; + const _AUTOGEN_LITERALS = /*autogen*/[ + [ + \nur\b\params\parametrable_utils::class, + '\\nur\\b\\params\\parametrable_utils::class', + ], + [ + self::PARAMETRABLE_PARAMS_SCHEMA, + 'self::PARAMETRABLE_PARAMS_SCHEMA', + ], + ]; + const _AUTOGEN_METHODS = /*autogen*/[ + [ + \nur\b\params\parametrable_utils::class, + '_autogen_methods_getters', + self::PARAMETRABLE_PARAMS_SCHEMA, + null, + ], + [ + \nur\b\params\parametrable_utils::class, + '_autogen_methods_setters', + self::PARAMETRABLE_PARAMS_SCHEMA, + null, + ], + ]; + const _AUTO_GETTERS = /*autogen*/[ + 'getAction' => 'action', + 'getMaxCount' => 'max_count', + ]; + const _AUTO_SETTERS = /*autogen*/[ + 'setAction' => 'action', + 'setMaxCount' => 'max_count', + ]; + #--autogen-dynamic-- +} diff --git a/nur_src/mapper/item/StreamMapper_command.php b/nur_src/mapper/item/StreamMapper_command.php new file mode 100644 index 0000000..9869bed --- /dev/null +++ b/nur_src/mapper/item/StreamMapper_command.php @@ -0,0 +1,47 @@ + "faire des opérations sur le flux", + "action" => [self::class, "create_command"], + "cmd_args" => [ + "usage" => self::NAME, + ["-s", "--split", + "action" => [self::class, "set_action_split"], + ], + ["-j", "--join", "args" => [["value"]], "type" => "?int", + "action" => [self::class, "set_action_join"], + ], + ["-o", "--param", "args" => "value", + "action" => [self::class, "add_params"], + "help" => "spécifier une option générique", + ], + ], + ]; + + /** @var StreamMapper */ + protected static $command; + + static function create_command() { + self::$command = mappers_command::add(new StreamMapper()); + } + + static function set_action_split() { + self::get()->setActionSplit(); + } + + static function set_action_join(?int $maxCount) { + self::get()->setActionJoin($maxCount); + } +} diff --git a/nur_src/mapper/item/StringMapper.php b/nur_src/mapper/item/StringMapper.php new file mode 100644 index 0000000..b0d0e3e --- /dev/null +++ b/nur_src/mapper/item/StringMapper.php @@ -0,0 +1,143 @@ + 0) { + $pos = strpos($value, $from, $index); + if ($pos === false) break; + $value = substr($value, 0, $pos).$to.substr($value, $pos + $flength); + $index = $pos + $flength + 1; + } + return $value; + } + + protected static function join($value, string $sep): string { + if (base::z($value)) return $value; + return str::join($sep, A::with($value)); + } + + protected static function trunc($value, int $length, bool $ellips=false, ?string $suffix=null): string { + if (base::z($value)) return $value; + return str::trunc(strval($value), $length, $ellips, $suffix); + } + + protected static function trim($value): string { + if (base::z($value)) return $value; + return str::trim(strval($value)); + } + + protected static function ltrim($value): string { + if (base::z($value)) return $value; + return str::ltrim(strval($value)); + } + + protected static function rtrim($value): string { + if (base::z($value)) return $value; + return str::rtrim(strval($value)); + } + + protected static function lower($value): string { + if (base::z($value)) return $value; + return str::lower(strval($value)); + } + + protected static function lower1($value): string { + if (base::z($value)) return $value; + return str::lower1(strval($value)); + } + + protected static function upper($value): string { + if (base::z($value)) return $value; + return str::upper(strval($value)); + } + + protected static function upper1($value): string { + if (base::z($value)) return $value; + return str::upper1(strval($value)); + } + + protected static function upperw($value): string { + if (base::z($value)) return $value; + return str::upperw(strval($value)); + } + + ############################################################################# + const _AUTOGEN_CONSTS = [ + "" => [self::class, "_autogen_consts"], + ]; + const _AUTOGEN_LITERALS = /*autogen*/[ + [ + \nur\b\params\parametrable_utils::class, + '\\nur\\b\\params\\parametrable_utils::class', + ], + [ + self::PARAMETRABLE_PARAMS_SCHEMA, + 'self::PARAMETRABLE_PARAMS_SCHEMA', + ], + ]; + const _AUTOGEN_METHODS = /*autogen*/[ + [ + \nur\b\params\parametrable_utils::class, + '_autogen_methods_getters', + self::PARAMETRABLE_PARAMS_SCHEMA, + null, + ], + [ + \nur\b\params\parametrable_utils::class, + '_autogen_methods_setters', + self::PARAMETRABLE_PARAMS_SCHEMA, + null, + ], + ]; + const _AUTO_GETTERS = /*autogen*/[ + 'getActions' => 'actions', + 'isMarkedOnly' => 'marked_only', + ]; + const _AUTO_SETTERS = /*autogen*/[ + 'setActions' => 'actions', + 'setMarkedOnly' => 'marked_only', + ]; + #--autogen-dynamic-- +} diff --git a/nur_src/mapper/item/StringMapper_command.php b/nur_src/mapper/item/StringMapper_command.php new file mode 100644 index 0000000..6ea59f4 --- /dev/null +++ b/nur_src/mapper/item/StringMapper_command.php @@ -0,0 +1,51 @@ + "faire des opérations sur des chaines", + "action" => [self::class, "create_command"], + "cmd_args" => [ + "usage" => self::NAME, + ["--mo", "--marked-only", "args" => "value", "type" => "bool", + "action" => [self::class, "set_marked_only"], + "help" => "indiquer si les opérations ne doivent être effectuées que sur les éléments marqués", + ], + ["-o", "--param", "args" => "value", + "action" => [self::class, "add_params"], + "help" => "spécifier une option générique", + ], + ["args" => [["value", null]], + "action" => [self::class, "add_actions"], + ], + ], + ]; + + /** @var StringMapper */ + protected static $command; + + static function create_command() { + self::$command = mappers_command::add(new StringMapper()); + } + + static function set_marked_only(?bool $marked_only) { + self::get()->setMarkedOnly($marked_only); + } + + static function add_actions(array $actions) { + $command = self::get(); + foreach ($actions as $action) { + $command->addAction($action); + } + } +} diff --git a/nur_src/mapper/item/TextMapper.php b/nur_src/mapper/item/TextMapper.php new file mode 100644 index 0000000..e3b2685 --- /dev/null +++ b/nur_src/mapper/item/TextMapper.php @@ -0,0 +1,144 @@ + 0) { + $pos = mb_strpos($value, $from, $index); + if ($pos === false) break; + $value = mb_substr($value, 0, $pos).$to.mb_substr($value, $pos + $flength); + $index = $pos + $flength + 1; + } + return $value; + } + + protected static function join($value, string $sep): string { + if (base::z($value)) return $value; + return str::join($sep, A::with($value)); + } + + protected static function trunc($value, int $length, bool $ellips=false, ?string $suffix=null): string { + if (base::z($value)) return $value; + return txt::trunc(strval($value), $length, $ellips, $suffix); + } + + protected static function trim($value): string { + if (base::z($value)) return $value; + return txt::trim(strval($value)); + } + + protected static function ltrim($value): string { + if (base::z($value)) return $value; + return txt::ltrim(strval($value)); + } + + protected static function rtrim($value): string { + if (base::z($value)) return $value; + return txt::rtrim(strval($value)); + } + + protected static function lower($value): string { + if (base::z($value)) return $value; + return txt::lower(strval($value)); + } + + protected static function lower1($value): string { + if (base::z($value)) return $value; + return txt::lower1(strval($value)); + } + + protected static function upper($value): string { + if (base::z($value)) return $value; + return txt::upper(strval($value)); + } + + protected static function upper1($value): string { + if (base::z($value)) return $value; + return txt::upper1(strval($value)); + } + + protected static function upperw($value): string { + if (base::z($value)) return $value; + return txt::upperw(strval($value)); + } + + ############################################################################# + const _AUTOGEN_CONSTS = [ + "" => [self::class, "_autogen_consts"], + ]; + const _AUTOGEN_LITERALS = /*autogen*/[ + [ + \nur\b\params\parametrable_utils::class, + '\\nur\\b\\params\\parametrable_utils::class', + ], + [ + self::PARAMETRABLE_PARAMS_SCHEMA, + 'self::PARAMETRABLE_PARAMS_SCHEMA', + ], + ]; + const _AUTOGEN_METHODS = /*autogen*/[ + [ + \nur\b\params\parametrable_utils::class, + '_autogen_methods_getters', + self::PARAMETRABLE_PARAMS_SCHEMA, + null, + ], + [ + \nur\b\params\parametrable_utils::class, + '_autogen_methods_setters', + self::PARAMETRABLE_PARAMS_SCHEMA, + null, + ], + ]; + const _AUTO_GETTERS = /*autogen*/[ + 'getActions' => 'actions', + 'isMarkedOnly' => 'marked_only', + ]; + const _AUTO_SETTERS = /*autogen*/[ + 'setActions' => 'actions', + 'setMarkedOnly' => 'marked_only', + ]; + #--autogen-dynamic-- +} diff --git a/nur_src/mapper/item/TextMapper_command.php b/nur_src/mapper/item/TextMapper_command.php new file mode 100644 index 0000000..a906678 --- /dev/null +++ b/nur_src/mapper/item/TextMapper_command.php @@ -0,0 +1,51 @@ + "faire des opérations sur des chaines", + "action" => [self::class, "create_command"], + "cmd_args" => [ + "usage" => self::NAME, + ["--mo", "--marked-only", "args" => "value", "type" => "bool", + "action" => [self::class, "set_marked_only"], + "help" => "indiquer si les opérations ne doivent être effectuées que sur les éléments marqués", + ], + ["-o", "--param", "args" => "value", + "action" => [self::class, "add_params"], + "help" => "spécifier une option générique", + ], + ["args" => [["value", null]], + "action" => [self::class, "add_actions"], + ], + ], + ]; + + /** @var TextMapper */ + protected static $command; + + static function create_command() { + self::$command = mappers_command::add(new TextMapper()); + } + + static function set_marked_only(?bool $marked_only) { + self::get()->setMarkedOnly($marked_only); + } + + static function add_actions(array $actions) { + $command = self::get(); + foreach ($actions as $action) { + $command->addAction($action); + } + } +} diff --git a/nur_src/mapper/json/JsonReader.php b/nur_src/mapper/json/JsonReader.php new file mode 100644 index 0000000..ef2ce3e --- /dev/null +++ b/nur_src/mapper/json/JsonReader.php @@ -0,0 +1,112 @@ +pp_setInput($input); + } + + const PARAMETRABLE_PARAMS_SCHEMA = [ + "input" => [null, null, "fichier en entrée"], + "parse_whole" => ["bool", false, "faut-il analyser un objet par ligne"], + ]; + + protected $ppInput; + + function pp_setInput($input): void { + if ($input instanceof IReader) $this->reader = $input; + else $this->ppInput = $input; + } + + /** @var bool */ + protected $ppParseWhole = false; + + /** @var IReader */ + protected $reader; + + protected function setup(): void { + if ($this->reader === null) { + $this->reader = reader::with($this->ppInput); + } + } + + function producer() { + $reader = $this->reader; + $flags = JSON_THROW_ON_ERROR; + if ($this->ppParseWhole) { + $contents = $reader->readContents(); + yield json_decode($contents, true, 512, $flags); + } else { + while (true) { + try { + $line = $reader->readLine(); + yield json_decode($line, true, 512, $flags); + } catch (EOFException $e) { + break; + } + } + } + } + + protected function teardown(): void { + if ($this->reader !== null) { + $this->reader->close(); + $this->reader = null; + } + } + + ############################################################################# + const _AUTOGEN_CONSTS = [ + "" => [self::class, "_autogen_consts"], + ]; + const _AUTOGEN_LITERALS = /*autogen*/[ + [ + \nur\b\params\parametrable_utils::class, + '\\nur\\b\\params\\parametrable_utils::class', + ], + [ + self::PARAMETRABLE_PARAMS_SCHEMA, + 'self::PARAMETRABLE_PARAMS_SCHEMA', + ], + ]; + const _AUTOGEN_METHODS = /*autogen*/[ + [ + \nur\b\params\parametrable_utils::class, + '_autogen_methods_getters', + self::PARAMETRABLE_PARAMS_SCHEMA, + null, + ], + [ + \nur\b\params\parametrable_utils::class, + '_autogen_methods_setters', + self::PARAMETRABLE_PARAMS_SCHEMA, + null, + ], + ]; + const _AUTO_GETTERS = /*autogen*/[ + 'getInput' => 'input', + 'isParseWhole' => 'parse_whole', + ]; + const _AUTO_SETTERS = /*autogen*/[ + 'setInput' => 'input', + 'setParseWhole' => 'parse_whole', + ]; + #--autogen-dynamic-- +} diff --git a/nur_src/mapper/json/JsonReader_command.php b/nur_src/mapper/json/JsonReader_command.php new file mode 100644 index 0000000..37283b9 --- /dev/null +++ b/nur_src/mapper/json/JsonReader_command.php @@ -0,0 +1,45 @@ + "configurer le fichier en entrée au format JSON", + "action" => [self::class, "create_command"], + "cmd_args" => [ + "usage" => self::NAME." [file]", + ["-o", "--param", "args" => "value", + "action" => [self::class, "add_params"], + "help" => "spécifier une option générique", + ], + ["args" => [["file"]], + "action" => [self::class, "set_file"], + ], + ], + ]; + + const F_OPTION = ["-f", "--input", "args" => "file", + "action" => [self::class, "set_file"], + ]; + + /** @var JsonReader */ + private static $command; + + static function create_command() { + self::$command = new JsonReader(); + } + + static function set_file($file) { + $file = A::last(A::with($file)); + self::get()->setInput($file); + } +} diff --git a/nur_src/mapper/json/JsonWriter.php b/nur_src/mapper/json/JsonWriter.php new file mode 100644 index 0000000..8c8113d --- /dev/null +++ b/nur_src/mapper/json/JsonWriter.php @@ -0,0 +1,91 @@ +pp_setOutput($output); + } + + protected $ppPrettyPrint = false; + + protected $ppForceObject = false; + + const PARAMETRABLE_PARAMS_SCHEMA = [ + "output" => [null, null, "fichier en sortie"], + "pretty_print" => ["bool", true, "type d'affichage"], + "force_object" => ["bool", true, "forcer la sortie en objet"], + ]; + + protected function setup(): void { + parent::setup(); + $this->setupWriter(false); + } + + function cook($item) { + $options = JSON_UNESCAPED_SLASHES + JSON_UNESCAPED_UNICODE; + if ($this->ppPrettyPrint) $options += JSON_PRETTY_PRINT; + if ($this->ppForceObject) $options += JSON_FORCE_OBJECT; + $this->getWriter()->wnl(json_encode($item, $options)); + } + + protected function teardown(): void { + $this->teardownWriter(false); + } + + ############################################################################# + const _AUTOGEN_CONSTS = [ + "" => [self::class, "_autogen_consts"], + ]; + const _AUTOGEN_LITERALS = /*autogen*/[ + [ + \nur\b\params\parametrable_utils::class, + '\\nur\\b\\params\\parametrable_utils::class', + ], + [ + self::PARAMETRABLE_PARAMS_SCHEMA, + 'self::PARAMETRABLE_PARAMS_SCHEMA', + ], + ]; + const _AUTOGEN_METHODS = /*autogen*/[ + [ + \nur\b\params\parametrable_utils::class, + '_autogen_methods_getters', + self::PARAMETRABLE_PARAMS_SCHEMA, + null, + ], + [ + \nur\b\params\parametrable_utils::class, + '_autogen_methods_setters', + self::PARAMETRABLE_PARAMS_SCHEMA, + null, + ], + ]; + const _AUTO_GETTERS = /*autogen*/[ + 'getOutput' => 'output', + 'isPrettyPrint' => 'pretty_print', + 'isForceObject' => 'force_object', + ]; + const _AUTO_SETTERS = /*autogen*/[ + 'setOutput' => 'output', + 'setPrettyPrint' => 'pretty_print', + 'setForceObject' => 'force_object', + ]; + #--autogen-dynamic-- +} diff --git a/nur_src/mapper/json/JsonWriter_command.php b/nur_src/mapper/json/JsonWriter_command.php new file mode 100644 index 0000000..d2fcaa7 --- /dev/null +++ b/nur_src/mapper/json/JsonWriter_command.php @@ -0,0 +1,58 @@ + "configurer le fichier en sortie au format JSON", + "action" => [self::class, "create_command"], + "cmd_args" => [ + "usage" => self::NAME." [file]", + ["-p", "--pretty-print", + "action" => [self::class, "set_pretty_print", true], + ], + ["-j", "--force-object", + "action" => [self::class, "set_force_object", true], + ], + ["-o", "--param", "args" => "value", + "action" => [self::class, "add_params"], + "help" => "spécifier une option générique", + ], + ["args" => [["file"]], + "action" => [self::class, "set_file"], + ], + ], + ]; + + const O_OPTION = ["-o", "--output", "args" => "file", + "action" => [self::class, "set_file"], + ]; + + /** @var JsonWriter */ + private static $command; + + static function create_command() { + self::$command = new JsonWriter(); + } + + static function set_file($file) { + $file = A::last(A::with($file)); + self::get()->setOutput($file); + } + + static function set_pretty_print(bool $pretty_print) { + self::get()->setPrettyPrint($pretty_print); + } + static function set_force_object(bool $force_object) { + self::get()->setForceObject($force_object); + } +} diff --git a/nur_src/mapper/json/YamlReader.php b/nur_src/mapper/json/YamlReader.php new file mode 100644 index 0000000..d43d616 --- /dev/null +++ b/nur_src/mapper/json/YamlReader.php @@ -0,0 +1,91 @@ +pp_setInput($input); + } + + const PARAMETRABLE_PARAMS_SCHEMA = [ + "input" => [null, null, "fichier en entrée"], + ]; + + protected $ppInput; + + function pp_setInput($input): void { + if ($input instanceof IReader) $this->reader = $input; + else $this->ppInput = $input; + } + + /** @var IReader */ + protected $reader; + + protected function setup(): void { + if ($this->reader === null) { + $this->reader = reader::with($this->ppInput); + } + } + + function producer() { + $flags = Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE; + yield Yaml::parse($this->reader->readContents(), $flags); + } + + protected function teardown(): void { + if ($this->reader !== null) { + $this->reader->close(); + $this->reader = null; + } + } + + ############################################################################# + const _AUTOGEN_CONSTS = [ + "" => [self::class, "_autogen_consts"], + ]; + const _AUTOGEN_LITERALS = /*autogen*/[ + [ + \nur\b\params\parametrable_utils::class, + '\\nur\\b\\params\\parametrable_utils::class', + ], + [ + self::PARAMETRABLE_PARAMS_SCHEMA, + 'self::PARAMETRABLE_PARAMS_SCHEMA', + ], + ]; + const _AUTOGEN_METHODS = /*autogen*/[ + [ + \nur\b\params\parametrable_utils::class, + '_autogen_methods_getters', + self::PARAMETRABLE_PARAMS_SCHEMA, + null, + ], + [ + \nur\b\params\parametrable_utils::class, + '_autogen_methods_setters', + self::PARAMETRABLE_PARAMS_SCHEMA, + null, + ], + ]; + const _AUTO_GETTERS = /*autogen*/[ + 'getInput' => 'input', + ]; + const _AUTO_SETTERS = /*autogen*/[ + 'setInput' => 'input', + ]; + #--autogen-dynamic-- +} diff --git a/nur_src/mapper/json/YamlReader_command.php b/nur_src/mapper/json/YamlReader_command.php new file mode 100644 index 0000000..cd07739 --- /dev/null +++ b/nur_src/mapper/json/YamlReader_command.php @@ -0,0 +1,45 @@ + "configurer le fichier en entrée au format YAML", + "action" => [self::class, "create_command"], + "cmd_args" => [ + "usage" => self::NAME." [file]", + ["-o", "--param", "args" => "value", + "action" => [self::class, "add_params"], + "help" => "spécifier une option générique", + ], + ["args" => [["file"]], + "action" => [self::class, "set_file"], + ], + ], + ]; + + const F_OPTION = ["-f", "--input", "args" => "file", + "action" => [self::class, "set_file"], + ]; + + /** @var YamlReader */ + private static $command; + + static function create_command() { + self::$command = new YamlReader(); + } + + static function set_file($file) { + $file = A::last(A::with($file)); + self::get()->setInput($file); + } +} diff --git a/nur_src/mapper/json/YamlWriter.php b/nur_src/mapper/json/YamlWriter.php new file mode 100644 index 0000000..b703ac3 --- /dev/null +++ b/nur_src/mapper/json/YamlWriter.php @@ -0,0 +1,84 @@ +pp_setOutput($output); + } + + const PARAMETRABLE_PARAMS_SCHEMA = [ + "output" => [null, null, "fichier en sortie"], + ]; + + protected function setup(): void { + parent::setup(); + $this->setupWriter(false); + $this->firstObject = true; + } + + private $firstObject; + + function cook($item) { + $writer = $this->getWriter(); + + if ($this->firstObject) $this->firstObject = false; + else $writer->wnl("---"); + + $flags = Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK; + $writer->write(Yaml::dump($item, 20, 4, $flags)); + } + + protected function teardown(): void { + $this->teardownWriter(false); + } + + ############################################################################# + const _AUTOGEN_CONSTS = [ + "" => [self::class, "_autogen_consts"], + ]; + const _AUTOGEN_LITERALS = /*autogen*/[ + [ + \nur\b\params\parametrable_utils::class, + '\\nur\\b\\params\\parametrable_utils::class', + ], + [ + self::PARAMETRABLE_PARAMS_SCHEMA, + 'self::PARAMETRABLE_PARAMS_SCHEMA', + ], + ]; + const _AUTOGEN_METHODS = /*autogen*/[ + [ + \nur\b\params\parametrable_utils::class, + '_autogen_methods_getters', + self::PARAMETRABLE_PARAMS_SCHEMA, + null, + ], + [ + \nur\b\params\parametrable_utils::class, + '_autogen_methods_setters', + self::PARAMETRABLE_PARAMS_SCHEMA, + null, + ], + ]; + const _AUTO_GETTERS = /*autogen*/[ + 'getOutput' => 'output', + ]; + const _AUTO_SETTERS = /*autogen*/[ + 'setOutput' => 'output', + ]; + #--autogen-dynamic-- +} diff --git a/nur_src/mapper/json/YamlWriter_command.php b/nur_src/mapper/json/YamlWriter_command.php new file mode 100644 index 0000000..b8d46b9 --- /dev/null +++ b/nur_src/mapper/json/YamlWriter_command.php @@ -0,0 +1,45 @@ + "file", + "action" => [self::class, "set_file"], + ]; + + const NAME = "outyaml"; + const DEF = [self::NAME, "outl", + "help" => "configurer le fichier en sortie au format YAML", + "action" => [self::class, "create_command"], + "cmd_args" => [ + "usage" => self::NAME." [file]", + ["-o", "--param", "args" => "value", + "action" => [self::class, "add_params"], + "help" => "spécifier une option générique", + ], + ["args" => [["file"]], + "action" => [self::class, "set_file"], + ], + ], + ]; + + /** @var YamlWriter */ + private static $command; + + static function create_command() { + self::$command = new YamlWriter(); + } + + static function set_file($file) { + $file = A::last(A::with($file)); + self::get()->setOutput($file); + } +} diff --git a/nur_src/mapper/line/IconvMapper.php b/nur_src/mapper/line/IconvMapper.php new file mode 100644 index 0000000..a883859 --- /dev/null +++ b/nur_src/mapper/line/IconvMapper.php @@ -0,0 +1,107 @@ +pp_setInputEncoding($inputEncoding); + $this->pp_setOutputEncoding(null); + } + + const PARAMETRABLE_PARAMS_SCHEMA = encoding_utils::PARAMETRABLE_PARAMS_SCHEMA; + + protected $ppInputEncoding; + + function pp_setInputEncoding(?string $inputEncoding): self { + if ($inputEncoding === null) $inputEncoding = $this->INPUT_ENCODING(); + $this->ppInputEncoding = $inputEncoding; + return $this; + } + + protected $ppOutputEncoding; + + function pp_setOutputEncoding(?string $outputEncoding): self { + if ($outputEncoding === null) $outputEncoding = $this->OUTPUT_ENCODING(); + $this->ppOutputEncoding = $outputEncoding; + return $this; + } + + function _cook(&$values, string $fromEncoding, string $toEncoding): void { + if (is_array($values)) { + foreach ($values as &$value) { + $this->_cook($value, $fromEncoding, $toEncoding); + }; unset($value); + } else { + $values = iconv($fromEncoding, $toEncoding, strval($values)); + } + } + + function mapper($item) { + $inputEncoding = $this->ppInputEncoding; + $outputEncoding = $this->ppOutputEncoding; + if ($inputEncoding !== null && $outputEncoding !== null) { + $this->_cook($item, $inputEncoding, $outputEncoding); + } + return $item; + } + + ############################################################################# + const _AUTOGEN_CONSTS = [ + "" => [self::class, "_autogen_consts"], + ]; + const _AUTOGEN_LITERALS = /*autogen*/[ + [ + \nur\b\params\parametrable_utils::class, + '\\nur\\b\\params\\parametrable_utils::class', + ], + [ + self::PARAMETRABLE_PARAMS_SCHEMA, + 'self::PARAMETRABLE_PARAMS_SCHEMA', + ], + ]; + const _AUTOGEN_METHODS = /*autogen*/[ + [ + \nur\b\params\parametrable_utils::class, + '_autogen_methods_getters', + self::PARAMETRABLE_PARAMS_SCHEMA, + null, + ], + [ + \nur\b\params\parametrable_utils::class, + '_autogen_methods_setters', + self::PARAMETRABLE_PARAMS_SCHEMA, + null, + ], + ]; + const _AUTO_GETTERS = /*autogen*/[ + 'getInputEncoding' => 'input_encoding', + 'getOutputEncoding' => 'output_encoding', + ]; + const _AUTO_SETTERS = /*autogen*/[ + 'setInputEncoding' => 'input_encoding', + 'setOutputEncoding' => 'output_encoding', + ]; + #--autogen-dynamic-- +} diff --git a/nur_src/mapper/line/IconvMapper_command.php b/nur_src/mapper/line/IconvMapper_command.php new file mode 100644 index 0000000..47d60a2 --- /dev/null +++ b/nur_src/mapper/line/IconvMapper_command.php @@ -0,0 +1,47 @@ + "convertir l'encoding", + "action" => [self::class, "create_command"], + "cmd_args" => [ + "usage" => self::NAME, + ["-f", "--from", "args" => "value", + "action" => [self::class, "set_input_encoding"], + ], + ["-t", "--to", "args" => "value", + "action" => [self::class, "set_output_encoding"], + ], + ["-o", "--param", "args" => "value", + "action" => [self::class, "add_params"], + "help" => "spécifier une option générique", + ], + ], + ]; + + /** @var IconvMapper */ + protected static $command; + + static function create_command() { + self::$command = mappers_command::add(new IconvMapper()); + } + + static function set_input_encoding(string $input_encoding) { + self::get()->setInputEncoding($input_encoding); + } + + static function set_output_encoding(string $output_encoding) { + self::get()->setOutputEncoding($output_encoding); + } +} diff --git a/nur_src/mapper/line/LineReader.php b/nur_src/mapper/line/LineReader.php new file mode 100644 index 0000000..1ff2367 --- /dev/null +++ b/nur_src/mapper/line/LineReader.php @@ -0,0 +1,93 @@ +pp_setInput($input); + } + + function setEncodingFilter(string $from, string $to="utf-8"): void { + $this->_setEncodingFilter($from, $to); + } + + const PARAMETRABLE_PARAMS_SCHEMA = [ + "input" => [null, null, "fichier en entrée"], + ]; + + static function _get_parametrable_params_schema(): array { + return array_merge(self::PARAMETRABLE_PARAMS_SCHEMA + , encoding_utils::PARAMETRABLE_PARAMS_SCHEMA); + } + + protected function afterSetParametrableParams(array $modifiedKeys, ?Metadata $md=null): void { + $this->encodingInput__afterSetParametrableParams($modifiedKeys); + } + + protected $ppInput; + + function pp_setInput($input): void { + if ($input instanceof IReader) $this->reader = $input; + else $this->ppInput = $input; + } + + /** @var IReader */ + protected $reader; + + protected function setup(): void { + if ($this->reader === null) { + $this->reader = reader::with($this->ppInput); + $this->_rwAppendFilters($this->reader); + } + } + + function producer() { + $reader = $this->reader; + while (true) { + try { + yield $reader->readLine(); + } catch (EOFException $e) { + break; + } + } + } + + protected function teardown(): void { + if ($this->reader !== null) { + $this->reader->close(); + $this->reader = null; + } + } + + ############################################################################# + const _AUTOGEN_CONSTS = [ + "" => [self::class, "_autogen_consts", true], + ]; + const _AUTOGEN_LITERALS = null; + const _AUTOGEN_METHODS = null; + const _AUTO_GETTERS = null; + const _AUTO_SETTERS = null; + #--autogen-dynamic-- +} diff --git a/nur_src/mapper/line/LineReader_command.php b/nur_src/mapper/line/LineReader_command.php new file mode 100644 index 0000000..fd43852 --- /dev/null +++ b/nur_src/mapper/line/LineReader_command.php @@ -0,0 +1,55 @@ + "configurer le fichier en entrée pour lecture ligne par ligne", + "action" => [self::class, "create_command"], + "cmd_args" => [ + "usage" => self::NAME." [file]", + ["-e", "--input-encoding", "args" => "value", + "action" => [self::class, "set_input_encoding"], + ], + ["-o", "--param", "args" => "value", + "action" => [self::class, "add_params"], + "help" => "spécifier une option générique", + ], + ["args" => [["file"]], + "action" => [self::class, "set_file"], + ], + ], + ]; + + const F_OPTION = ["-f", "--input", "args" => "file", + "action" => [self::class, "set_file"], + ]; + const E_OPTION = ["-e", "--input-encoding", "args" => "value", + "action" => [self::class, "set_input_encoding"], + ]; + + /** @var LineReader */ + protected static $command; + + static function create_command() { + self::$command = new LineReader(); + } + + static function set_file($file) { + $file = A::last(A::with($file)); + self::get()->setInput($file); + } + + static function set_input_encoding(string $input_encoding) { + self::get()->setEncodingFilter($input_encoding); + } +} diff --git a/nur_src/mapper/line/LineWriter.php b/nur_src/mapper/line/LineWriter.php new file mode 100644 index 0000000..9a4597f --- /dev/null +++ b/nur_src/mapper/line/LineWriter.php @@ -0,0 +1,67 @@ +pp_setOutput($output); + } + + function setEncodingFilter(string $to, string $from="utf-8"): void { + $this->_setEncodingFilter($from, $to); + } + + const PARAMETRABLE_PARAMS_SCHEMA = [ + "output" => [null, null, "fichier en sortie"], + ]; + + static function _get_parametrable_params_schema(): array { + return array_merge(self::PARAMETRABLE_PARAMS_SCHEMA + , encoding_utils::PARAMETRABLE_PARAMS_SCHEMA); + } + + protected function afterSetParametrableParams(array $modifiedKeys, ?Metadata $md=null): void { + $this->encodingOutput__afterSetParametrableParams($modifiedKeys); + } + + protected function setup(): void { + parent::setup(); + $this->setupWriter(true); + } + + function cook($item) { + $this->getWriter()->wnl($item); + } + + protected function teardown(): void { + $this->teardownWriter(true); + } + + ############################################################################# + const _AUTOGEN_CONSTS = [ + "" => [self::class, "_autogen_consts", true], + ]; + const _AUTOGEN_LITERALS = null; + const _AUTOGEN_METHODS = null; + const _AUTO_GETTERS = null; + const _AUTO_SETTERS = null; + #--autogen-dynamic-- +} diff --git a/nur_src/mapper/line/LineWriter_command.php b/nur_src/mapper/line/LineWriter_command.php new file mode 100644 index 0000000..a9fbc9a --- /dev/null +++ b/nur_src/mapper/line/LineWriter_command.php @@ -0,0 +1,55 @@ + "configurer le fichier en sortie pour écriture ligne par ligne", + "action" => [self::class, "create_command"], + "cmd_args" => [ + "usage" => self::NAME." [file]", + ["-t", "--output-encoding", "args" => "value", + "action" => [self::class, "set_output_encoding"], + ], + ["-o", "--param", "args" => "value", + "action" => [self::class, "add_params"], + "help" => "spécifier une option générique", + ], + ["args" => [["file"]], + "action" => [self::class, "set_file"], + ], + ], + ]; + + const O_OPTION = ["-o", "--output", "args" => "file", + "action" => [self::class, "set_file"], + ]; + const T_OPTION = ["-t", "--output-encoding", "args" => "value", + "action" => [self::class, "set_output_encoding"], + ]; + + /** @var LineWriter */ + protected static $command; + + static function create_command() { + self::$command = new LineWriter(); + } + + static function set_file($file) { + $file = A::last(A::with($file)); + self::get()->setOutput($file); + } + + static function set_output_encoding(string $output_encoding) { + self::get()->setEncodingFilter($output_encoding); + } +} diff --git a/nur_src/mapper/pl.php b/nur_src/mapper/pl.php new file mode 100644 index 0000000..28b22d5 --- /dev/null +++ b/nur_src/mapper/pl.php @@ -0,0 +1,17 @@ +consume(); + return $consumer; + } +} diff --git a/nur_src/t/TestCase.php b/nur_src/t/TestCase.php new file mode 100644 index 0000000..8b965f3 --- /dev/null +++ b/nur_src/t/TestCase.php @@ -0,0 +1,53 @@ +origTz = date_default_timezone_get(); + date_default_timezone_set("Indian/Reunion"); + } + + protected function tearDown(): void { + date_default_timezone_set($this->origTz); + } +} diff --git a/nur_tbin/datareader.php b/nur_tbin/datareader.php new file mode 100755 index 0000000..9064e2b --- /dev/null +++ b/nur_tbin/datareader.php @@ -0,0 +1,7 @@ +#!/usr/bin/php + [10, "string"], + "prenom" => [10, "string"], + "age" => [3, "number"], +]; diff --git a/nur_tbin/samples/json_data.json b/nur_tbin/samples/json_data.json new file mode 100644 index 0000000..d856163 --- /dev/null +++ b/nur_tbin/samples/json_data.json @@ -0,0 +1,2 @@ +{"nom":"clain","prenom":"jephté","age":45} +{"nom":"clain","prenom":"françoise","age":53} diff --git a/nur_tbin/samples/line_data.txt b/nur_tbin/samples/line_data.txt new file mode 100644 index 0000000..a8358e6 --- /dev/null +++ b/nur_tbin/samples/line_data.txt @@ -0,0 +1,9 @@ +nom +prenom +age +clain +jephté +45 +clain +françoise +53 diff --git a/nur_tbin/samples/yaml_data.yml b/nur_tbin/samples/yaml_data.yml new file mode 100644 index 0000000..12c90a6 --- /dev/null +++ b/nur_tbin/samples/yaml_data.yml @@ -0,0 +1,8 @@ +- + nom: clain + prenom: jephté + age: 45 +- + nom: clain + prenom: françoise + age: 53 diff --git a/nur_tests/mapper/base/ConsumerTest.php b/nur_tests/mapper/base/ConsumerTest.php new file mode 100644 index 0000000..e38292a --- /dev/null +++ b/nur_tests/mapper/base/ConsumerTest.php @@ -0,0 +1,135 @@ +result[] = $item; + } + }; + $consumer->consume(); + self::assertSame([4, 6, 8, 10], $consumer->result); + } + + function testConsumer2() { + $consumer = new ResultConsumer(NumberProducer::class); + $consumer->add(PlusOneMapper::class); + $consumer->add(TwiceMapper::class); + $consumer->consume(); + self::assertSame([4, 6, 8, 10], $consumer->result); + } + + function testConsumer3() { + $consumer = new ResultConsumer([1, 2, 3, 4], PlusOneMapper::class, TwiceMapper::class); + $consumer->consume(); + self::assertSame([4, 6, 8, 10], $consumer->result); + } + + function testConsumer4() { + $consumer = new class extends Consumer { + private $count; + function getCount(): int { + return $this->count; + } + + private $sum; + function getSum(): int { + return $this->sum; + } + + function cook($item) { + $this->count++; + $this->sum += $item; + } + }; + $consumer->consume([1, 2, 3, 4], function ($value) { + return $value + 1; + }); + self::assertSame(4, $consumer->getCount()); + self::assertSame(14, $consumer->getSum()); + } + + function testConsumer5() { + $consumer = new ResultConsumer([1, 2, 3, 4], [AddMapper::class, 1]); + $consumer->consume(); + self::assertSame([2, 3, 4, 5], $consumer->result); + } + + function testConsumer6() { + $consumer = new ResultConsumer(null, [AddMapper::class, 1]); + $consumer->push(1); + self::assertSame([2], $consumer->result); + $consumer->pushAll([2, 3]); + self::assertSame([2, 3, 4], $consumer->result); + $consumer->push(4); + self::assertSame([2, 3, 4, 5], $consumer->result); + $consumer->close(); + } + + function testPushMix() { + $consumer = new ResultConsumer(NumberProducer::class, [AddMapper::class, 1]); + $consumer->push(10); + self::assertSame([11], $consumer->result); + $consumer->consume(); + self::assertSame([11], $consumer->result); + $consumer->close(); + $consumer->consume(); + self::assertSame([11, 2, 3, 4, 5], $consumer->result); + + $consumer = new ResultConsumer(NumberProducer::class, [AddMapper::class, 1]); + $consumer->consume(); + self::assertSame([2, 3, 4, 5], $consumer->result); + $consumer->push(10); + self::assertSame([2, 3, 4, 5, 11], $consumer->result); + $consumer->close(); + + $consumer = new ResultConsumer(NumberProducer::class, [AddMapper::class, 1]); + $consumer->consume(); + self::assertSame([2, 3, 4, 5], $consumer->result); + $consumer->consume(); + self::assertSame([2, 3, 4, 5, 2, 3, 4, 5], $consumer->result); + $consumer->push(10); + self::assertSame([2, 3, 4, 5, 2, 3, 4, 5, 11], $consumer->result); + $consumer->consume(); + self::assertSame([2, 3, 4, 5, 2, 3, 4, 5, 11], $consumer->result); + $consumer->close(); + $consumer->consume(); + self::assertSame([2, 3, 4, 5, 2, 3, 4, 5, 11, 2, 3, 4, 5], $consumer->result); + } + + function testDefaultArgs() { + $consumer = new ResultConsumer(NumberProducer::class + , [Add2Mapper::class, 1] # 1 + , [Add2Mapper::class, 1, 2] # 3 + , [Add2Mapper::class, 1, 2, 3] # 6 + ); + $consumer->consume(); + self::assertSame([11, 12, 13, 14], $consumer->result); + } +} diff --git a/nur_tests/mapper/base/MapperAggregateTest.php b/nur_tests/mapper/base/MapperAggregateTest.php new file mode 100644 index 0000000..06aa34d --- /dev/null +++ b/nur_tests/mapper/base/MapperAggregateTest.php @@ -0,0 +1,20 @@ +add(PlusOneMapper::class); + $mapper->add(MultMapper::class); + + $consumer = new ResultConsumer([1, 2, 3], $mapper); + $consumer->consume(); + self::assertSame([2, 4, 3, 6, 4, 8], $consumer->getResult()); + } +} + diff --git a/nur_tests/mapper/base/MapperTest.php b/nur_tests/mapper/base/MapperTest.php new file mode 100644 index 0000000..c2dfb1a --- /dev/null +++ b/nur_tests/mapper/base/MapperTest.php @@ -0,0 +1,183 @@ + "b", 2, "c" => "d"]; + + const STRINGS = ["a", "bb", "ccc", "dddd"]; + const SPLIT = ["b", "b", "c", "cc", "dd", "dd"]; + const SPLIT2 = ["", "", "b", "-", "b", "", "c", "-", "cc", "", "dd", "-", "dd"]; + + function testReturn() { + $mapper = new IdentReturnMapper(self::NUMBERS); + self::assertSame(self::NUMBERS, iterator_to_array($mapper)); + + $mapper = new IdentReturnMapper(self::ASSOC); + self::assertSame(self::ASSOC, iterator_to_array($mapper)); + + $mapper = new class(self::NUMBERS) extends Mapper { + function mapper($item) { + return $item * 2; + } + }; + self::assertSame(self::DOUBLE, iterator_to_array($mapper)); + } + + function testFilter() { + $mapper = new class(self::NUMBERS) extends Mapper { + function mapper($item) { + return $item <= 2 ? $item : null; + } + }; + self::assertSame(self::FIRST, iterator_to_array($mapper)); + + $mapper = new class(self::NUMBERS) extends Mapper { + function mapper($item) { + return $item > 2 ? $item : null; + } + }; + self::assertSame(self::LAST, iterator_to_array($mapper)); + } + + static function split($value): ?array { + $length = strlen($value); + if ($length < 2) return null; + $middle = intdiv($length, 2); + $first = substr($value, 0, $middle); + $last = substr($value, $middle); + return [$first, $last]; + } + + function testMapTo() { + $mapper = new IdentMapToMapper(self::NUMBERS); + self::assertSame(self::NUMBERS, iterator_to_array($mapper)); + + $mapper = new IdentMapToMapper(self::ASSOC); + self::assertSame(self::ASSOC, iterator_to_array($mapper)); + + $mapper = new class(self::NUMBERS) extends Mapper { + function mapper($item) { + $this->mapToValue($item * 2); + } + }; + self::assertSame(self::DOUBLE, iterator_to_array($mapper)); + + $mapper = new class(self::NUMBERS) extends Mapper { + function mapper($item) { + // possible d'utiliser return (la valeur est ignorée) + return $item > 2 ? $this->mapToValue($item * 2) : $this->mapToNone(); + } + }; + self::assertSame(self::LAST_DOUBLE, iterator_to_array($mapper)); + + $mapper = new class(self::STRINGS) extends Mapper { + function mapper($item) { + return $this->mapTo(MapperTest::split($item)); + } + }; + self::assertSame(self::SPLIT, iterator_to_array($mapper)); + + $mapper = new class(self::STRINGS) extends Mapper { + function splitemup($value) { + $parts = MapperTest::split($value); + if ($parts !== null) { + yield $parts[0]; + yield "-"; + yield $parts[1]; + } + } + + function mapper($item) { + $this->mapToValue("<$item>"); + $this->mapTo($this->splitemup($item)); + } + }; + self::assertSame(self::SPLIT2, iterator_to_array($mapper)); + } + + function testSofEof() { + self::assertSame(["a", "b"], iterator_to_array(new SofEofMapper(["a", "b"]))); + self::assertSame(["<", "a", "b"], iterator_to_array(new class(["a", "b"]) extends SofEofMapper { + const MAP_SOF = true; + })); + self::assertSame(["a", "b", ">"], iterator_to_array(new class(["a", "b"]) extends SofEofMapper { + const MAP_EOF = true; + })); + self::assertSame(["<", "a", "b", ">"], iterator_to_array(new class(["a", "b"]) extends SofEofMapper { + const MAP_SOF = true; + const MAP_EOF = true; + })); + } + + function testClose() { + $mapper1 = new class extends Mapper { + public $complete = false; + public function getIterator(): iterable { + yield from parent::getIterator(); + $this->complete = true; + } + + public $closed = false; + protected function teardown(): void { + parent::teardown(); + $this->closed = true; + } + public function mapper($item) { + return $item; + } + }; + $mapper2 = new class extends Mapper { + public $complete = false; + public function getIterator(): iterable { + yield from parent::getIterator(); + $this->complete = true; + } + + public $closed = false; + protected function teardown(): void { + parent::teardown(); + $this->closed = true; + } + public function mapper($item) { + return $item; + } + }; + $consumer = new class extends Consumer { + public $throw = true; + public function cook($item) { + if ($this->throw && $item >= 5) throw new ValueException("cinq"); + return $item + 1; + } + }; + + $mapper1->complete = $mapper1->closed = false; + $mapper2->complete = $mapper2->closed = false; + $consumer->throw = true; + self::assertException(ValueException::class, [$consumer, "consume"], [1, 2, 3, 5, 6, 7], $mapper1, $mapper2); + self::assertFalse($mapper1->complete); + self::assertTrue($mapper1->closed); + self::assertFalse($mapper2->complete); + self::assertTrue($mapper2->closed); + + $mapper1->complete = $mapper1->closed = false; + $mapper2->complete = $mapper2->closed = false; + $consumer->throw = false; + self::assertNotException([$consumer, "consume"], [1, 2, 3, 5, 6, 7], $mapper1, $mapper2); + self::assertTrue($mapper1->complete); + self::assertTrue($mapper1->closed); + self::assertTrue($mapper2->complete); + self::assertTrue($mapper2->closed); + } +} diff --git a/nur_tests/mapper/base/ProducerTest.php b/nur_tests/mapper/base/ProducerTest.php new file mode 100644 index 0000000..1bdf4f4 --- /dev/null +++ b/nur_tests/mapper/base/ProducerTest.php @@ -0,0 +1,31 @@ +close(); + self::assertSame(["first", "second"], $lines); + + $p = new LineReader(new StringReader("first\nsecond\n")); + $lines = []; + foreach ($p as $line) { + $lines[] = $line; + } + $p->close(); + self::assertSame(["first", "second"], $lines); + } +} diff --git a/nur_tests/mapper/base/capacitor/CapacitorTest.php b/nur_tests/mapper/base/capacitor/CapacitorTest.php new file mode 100644 index 0000000..dfe1924 --- /dev/null +++ b/nur_tests/mapper/base/capacitor/CapacitorTest.php @@ -0,0 +1,107 @@ +setKeys(["id" => true]); + $c->charge(["id" => 1, "title" => "first"]); + $c->charge(["id" => 1, "title" => "first (doublon)"]); + self::assertSame([ + "1" => ["id" => 1, "title" => "first (doublon)"], + ], $c->discharge()); + } + + function testSortSeq() { + $items = [ + ["second", 4, 2, 1], + ["hello", 1, 1, 4], + ["first", 3, 1, 2], + ["world", 2, 2, 3], + ]; + $c = new Capacitor(); + foreach ($items as $item) { + $c->charge($item); + } + + $c->sort([1]); + self::assertSame([ + ["hello", 1, 1, 4], + ["world", 2, 2, 3], + ["first", 3, 1, 2], + ["second", 4, 2, 1], + ], $c->discharge(null, false)); + + $c->sort([2]); + self::assertSame([ + ["hello", 1, 1, 4], + ["first", 3, 1, 2], + ["world", 2, 2, 3], + ["second", 4, 2, 1], + ], $c->discharge(null, false)); + + $c->sort([3]); + self::assertSame([ + ["second", 4, 2, 1], + ["first", 3, 1, 2], + ["world", 2, 2, 3], + ["hello", 1, 1, 4], + ], $c->discharge(null, false)); + + $c->sort([2, 3]); + self::assertSame([ + ["first", 3, 1, 2], + ["hello", 1, 1, 4], + ["second", 4, 2, 1], + ["world", 2, 2, 3], + ], $c->discharge(null, false)); + } + + function testSortAssoc() { + $items = [ + ["hello", 1, 1, 4], + ["world", 2, 2, 3], + ["first", 3, 1, 2], + ["second", 4, 2, 1], + ]; + $c = new Capacitor(); + $c->setKeys([[0, true]]); + foreach ($items as $item) { + $c->charge($item); + } + + $c->sort([1]); + self::assertSame([ + "hello" => ["hello", 1, 1, 4], + "world" => ["world", 2, 2, 3], + "first" => ["first", 3, 1, 2], + "second" => ["second", 4, 2, 1], + ], $c->discharge(null, false)); + + $c->sort([2]); + self::assertSame([ + "hello" => ["hello", 1, 1, 4], + "first" => ["first", 3, 1, 2], + "world" => ["world", 2, 2, 3], + "second" => ["second", 4, 2, 1], + ], $c->discharge(null, false)); + + $c->sort([3]); + self::assertSame([ + "second" => ["second", 4, 2, 1], + "first" => ["first", 3, 1, 2], + "world" => ["world", 2, 2, 3], + "hello" => ["hello", 1, 1, 4], + ], $c->discharge(null, false)); + + $c->sort([2, 3]); + self::assertSame([ + "first" => ["first", 3, 1, 2], + "hello" => ["hello", 1, 1, 4], + "second" => ["second", 4, 2, 1], + "world" => ["world", 2, 2, 3], + ], $c->discharge(null, false)); + } +} diff --git a/nur_tests/mapper/base/impl/Add2Mapper.php b/nur_tests/mapper/base/impl/Add2Mapper.php new file mode 100644 index 0000000..66bee65 --- /dev/null +++ b/nur_tests/mapper/base/impl/Add2Mapper.php @@ -0,0 +1,20 @@ +amount = $amount; + } + + private $amount; + + function mapper($item) { + return $item + $this->amount; + } +} \ No newline at end of file diff --git a/nur_tests/mapper/base/impl/AddMapper.php b/nur_tests/mapper/base/impl/AddMapper.php new file mode 100644 index 0000000..b9ce700 --- /dev/null +++ b/nur_tests/mapper/base/impl/AddMapper.php @@ -0,0 +1,17 @@ +amount = $amount; + } + + private $amount; + + function mapper($item) { + return $item + $this->amount; + } +} \ No newline at end of file diff --git a/nur_tests/mapper/base/impl/IdentMapToMapper.php b/nur_tests/mapper/base/impl/IdentMapToMapper.php new file mode 100644 index 0000000..6cb6140 --- /dev/null +++ b/nur_tests/mapper/base/impl/IdentMapToMapper.php @@ -0,0 +1,10 @@ +mapToValue($item, $key); + } +} \ No newline at end of file diff --git a/nur_tests/mapper/base/impl/IdentReturnMapper.php b/nur_tests/mapper/base/impl/IdentReturnMapper.php new file mode 100644 index 0000000..743521b --- /dev/null +++ b/nur_tests/mapper/base/impl/IdentReturnMapper.php @@ -0,0 +1,10 @@ +mapTo([$item, $item * 2]); + } +} \ No newline at end of file diff --git a/nur_tests/mapper/base/impl/NumberProducer.php b/nur_tests/mapper/base/impl/NumberProducer.php new file mode 100644 index 0000000..69ed130 --- /dev/null +++ b/nur_tests/mapper/base/impl/NumberProducer.php @@ -0,0 +1,10 @@ +result[] = $item; + } + + public $result; + + function getResult(): array { + return $this->result; + } +} \ No newline at end of file diff --git a/nur_tests/mapper/base/impl/SofEofMapper.php b/nur_tests/mapper/base/impl/SofEofMapper.php new file mode 100644 index 0000000..3654fe2 --- /dev/null +++ b/nur_tests/mapper/base/impl/SofEofMapper.php @@ -0,0 +1,12 @@ +sof) return "<"; + elseif (!$this->eof) return $item; + else return ">"; + } +} \ No newline at end of file diff --git a/nur_tests/mapper/base/impl/TwiceMapper.php b/nur_tests/mapper/base/impl/TwiceMapper.php new file mode 100644 index 0000000..7d6fa87 --- /dev/null +++ b/nur_tests/mapper/base/impl/TwiceMapper.php @@ -0,0 +1,10 @@ +consume([ + ["nom" => "clain", "prenom" => "jephté", "age" => 45], + ["nom" => "enpion", "prenom" => "tarte", "age" => 36], + ["nom" => "cool", "prenom" => "plusieurs +lignes", "age" => 15], + ], Assoc2CsvMapper::class); + self::assertSame("nom,prenom,age +clain,jephté,45 +enpion,tarte,36 +cool,\"plusieurs +lignes\",15 +", $w->getString()); + } + + function testMultischema() { + $w = new StringWriter(); + $consumer = new LineWriter($w); + $mapper = new Assoc2CsvMapper(); + $mapper->setMultiSchema(); + $consumer->consume([ + ["nom" => "clain", "prenom" => "jephté", "age" => 45], + ["prenom" => "tarte", "nom" => "enpion", "age" => 36], + ["a" => "first", "b" => "second"], + ], $mapper); + self::assertSame("nom,prenom,age +clain,jephté,45 +enpion,tarte,36 + +a,b +first,second +", $w->getString()); + } + + function testEmpty() { + $w = new StringWriter(); + $consumer = new LineWriter($w); + $consumer->consume([], Assoc2CsvMapper::class); + self::assertSame("", $w->getString()); + + $w = new StringWriter(); + $consumer = new LineWriter($w); + $mapper = new Assoc2CsvMapper(); + $mapper->setHeaders(["nom", "prenom", "age"]); + $consumer->consume([], $mapper); + self::assertSame("nom,prenom,age\n", $w->getString()); + } +} diff --git a/nur_tests/mapper/csv/Csv2AssocMapperTest.php b/nur_tests/mapper/csv/Csv2AssocMapperTest.php new file mode 100644 index 0000000..832dd6f --- /dev/null +++ b/nur_tests/mapper/csv/Csv2AssocMapperTest.php @@ -0,0 +1,114 @@ +setMultiSchema(); + $rows = iterator_to_array($mapper); + self::assertSame([ + ["nom" => "clain", "prenom" => "jephté", "age" => "45"], + ["nom" => "enpion", "prenom" => "tarte", "age" => "36"], + ["nom" => "x", "prenom" => "y", "age" => false], + ["nom" => "z", "prenom" => false, "age" => false], + ["a" => "first", "b" => "second"], + ], $rows); + } + + function testMapper2() { + $reader = new StringReader(<< "clain", "comment" => "\"ceci est", "num" => null], + ["nom" => "un commentaire\"", "comment" => "45", "num" => null], + ], $rows); + } + + function testMapper3() { + $reader = new StringReader(""); + + $rows = iterator_to_array(new Csv2AssocMapper(new LineReader($reader))); + self::assertSame([], $rows); + } + + function testMapper4() { + $reader = new StringReader("nom,prenom,age"); + + $rows = iterator_to_array(new Csv2AssocMapper(new LineReader($reader))); + self::assertSame([], $rows); + } + + function testMapper5() { + $reader = new StringReader("nom,prenom,age +clain,jephté,45 +enpion,, +flash"); + + $reader->seek(); + $mapper = new Csv2AssocMapper(new LineReader($reader)); + $rows = iterator_to_array($mapper); + self::assertSame([ + ["nom" => "clain", "prenom" => "jephté", "age" => "45"], + ["nom" => "enpion", "prenom" => "", "age" => ""], + ["nom" => "flash", "prenom" => false, "age" => false], + ], $rows); + + $reader->seek(); + $mapper = new Csv2AssocMapper(new LineReader($reader)); + $mapper->setMapEmpty(false); + $rows = iterator_to_array($mapper); + self::assertSame([ + ["nom" => "clain", "prenom" => "jephté", "age" => "45"], + ["nom" => "enpion", "prenom" => false, "age" => false], + ["nom" => "flash", "prenom" => false, "age" => false], + ], $rows); + + $reader->seek(); + $mapper = new Csv2AssocMapper(new LineReader($reader)); + $mapper->setMapEmpty(null); + $rows = iterator_to_array($mapper); + self::assertSame([ + ["nom" => "clain", "prenom" => "jephté", "age" => "45"], + ["nom" => "enpion", "prenom" => null, "age" => null], + ["nom" => "flash", "prenom" => false, "age" => false], + ], $rows); + } + + function testMapper6() { + $reader = new FileReader(__DIR__ . "/utf8.csv"); + $rows = iterator_to_array(new Csv2AssocMapper(new LineReader($reader))); + self::assertSame([ + ["nom" => "clain", "prénom" => "jephté", "age" => "45"], + ], $rows); + + $reader = new FileReader(__DIR__ . "/cp1252.csv"); + $rows = iterator_to_array(new Csv2AssocMapper(new IconvMapper("cp1252", new LineReader($reader)))); + self::assertSame([ + ["nom" => "clain", "prénom" => "jephté", "age" => "45"], + ], $rows); + } +} diff --git a/nur_tests/mapper/csv/CsvReaderTest.php b/nur_tests/mapper/csv/CsvReaderTest.php new file mode 100644 index 0000000..1b1a9f8 --- /dev/null +++ b/nur_tests/mapper/csv/CsvReaderTest.php @@ -0,0 +1,86 @@ +setMultiSchema(true); + $rows = iterator_to_array($reader); + self::assertSame([ + ["nom" => "clain", "prenom" => "jephté", "age" => "45"], + ["nom" => "enpion", "prenom" => "tarte", "age" => "36"], + ["nom" => "x", "prenom" => "y", "age" => false], + ["nom" => "z", "prenom" => false, "age" => false], + ["a" => "first", "b" => "second"], + ], $rows); + } + + function testReader2() { + $rwf = fopen("php://memory", "w+b"); + fwrite($rwf, << "clain", "comment" => "ceci est +un commentaire", "num" => "45"], + ], $rows); + } + + function testReader3() { + $rwf = fopen("php://memory", "w+b"); + fwrite($rwf, ""); + rewind($rwf); + + $reader = new CsvReader($rwf); + $rows = iterator_to_array($reader); + self::assertSame([], $rows); + } + + function testReader4() { + $rwf = fopen("php://memory", "w+b"); + fwrite($rwf, "nom,prenom,age"); + rewind($rwf); + + $reader = new CsvReader($rwf); + $rows = iterator_to_array($reader); + self::assertSame([], $rows); + } + + function testReader5() { + $reader = new CsvReader(__DIR__ . "/utf8.csv"); + $rows = iterator_to_array($reader); + self::assertSame([ + ["nom" => "clain", "prénom" => "jephté", "age" => "45"], + ], $rows); + + $reader = new CsvReader(__DIR__ . "/cp1252.csv"); + $reader->setEncodingFilter("cp1252"); + $rows = iterator_to_array($reader); + self::assertSame([ + ["nom" => "clain", "prénom" => "jephté", "age" => "45"], + ], $rows); + } +} diff --git a/nur_tests/mapper/csv/CsvWriterTest.php b/nur_tests/mapper/csv/CsvWriterTest.php new file mode 100644 index 0000000..7033db3 --- /dev/null +++ b/nur_tests/mapper/csv/CsvWriterTest.php @@ -0,0 +1,55 @@ +consume([ + ["nom" => "clain", "prenom" => "jephté", "age" => 45], + ["nom" => "enpion", "prenom" => "tarte", "age" => 36], + ["nom" => "cool", "prenom" => "plusieurs +lignes", "age" => 15], + ]); + self::assertSame("nom,prenom,age +clain,jephté,45 +enpion,tarte,36 +cool,\"plusieurs +lignes\",15 +", $w->getString()); + } + + function testResource() { + $w = fopen("php://memory", "w+b"); + $consumer = new CsvWriter($w); + $consumer->consume([ + ["nom" => "clain", "prenom" => "jephté", "age" => 45], + ["nom" => "enpion", "prenom" => "tarte", "age" => 36], + ["nom" => "cool", "prenom" => "plusieurs +lignes", "age" => 15], + ]); + rewind($w); + self::assertSame("nom,prenom,age +clain,jephté,45 +enpion,tarte,36 +cool,\"plusieurs +lignes\",15 +", stream_get_contents($w)); + } + + function testEmpty() { + $w = new StringWriter(); + $consumer = new CsvWriter($w); + $consumer->consume([]); + self::assertSame("", $w->getString()); + + $w = new StringWriter(); + $consumer = new CsvWriter($w); + $consumer->setHeaders(["nom", "prenom", "age"]); + $consumer->consume([]); + self::assertSame("nom,prenom,age\n", $w->getString()); + } +} diff --git a/nur_tests/mapper/csv/cp1252.csv b/nur_tests/mapper/csv/cp1252.csv new file mode 100644 index 0000000..8195f10 --- /dev/null +++ b/nur_tests/mapper/csv/cp1252.csv @@ -0,0 +1,2 @@ +nom,prénom,age +clain,jephté,45 diff --git a/nur_tests/mapper/csv/utf8.csv b/nur_tests/mapper/csv/utf8.csv new file mode 100644 index 0000000..f12f0c3 --- /dev/null +++ b/nur_tests/mapper/csv/utf8.csv @@ -0,0 +1,2 @@ +nom,prénom,age +clain,jephté,45 diff --git a/nur_tests/mapper/fsv/Fsv2AssocMapperTest.php b/nur_tests/mapper/fsv/Fsv2AssocMapperTest.php new file mode 100644 index 0000000..aef2563 --- /dev/null +++ b/nur_tests/mapper/fsv/Fsv2AssocMapperTest.php @@ -0,0 +1,48 @@ + [10, "string"], + "prenom" => [10, "string"], + "date_nai" => [8, "date"], + "exact" => [6, "number", 2], + "approx" => [6, "number"], + ]; + + $mapper = new Fsv2AssocMapper($schema, $source); + $result = iterator_to_array($mapper); + self::assertSame([ + [ + "nom" => "Clain", + "prenom" => "Jephte", + "date_nai" => "29/06/1975", + "exact" => 15.0, + "approx" => 153, + ], + [ + "nom" => "", + "prenom" => "Francoise", + "date_nai" => "29/05/1959", + "exact" => false, + "approx" => false, + ], + ], $result); + + $mapper = new Fsv2AssocMapper($schema, $source); + $mapper->setOutputSeq(true); + $result = iterator_to_array($mapper); + self::assertSame([ + ["nom", "prenom", "date_nai", "exact", "approx"], + ["Clain", "Jephte", "29/06/1975", 15.0, 153], + ["", "Francoise", "29/05/1959", false, false], + ], $result); + } +} diff --git a/nur_tests/mapper/fsv/FsvReaderTest.php b/nur_tests/mapper/fsv/FsvReaderTest.php new file mode 100644 index 0000000..d82a32b --- /dev/null +++ b/nur_tests/mapper/fsv/FsvReaderTest.php @@ -0,0 +1,87 @@ + [10, "string"], + "prenom" => [10, "string"], + "date_nai" => [8, "date"], + "exact" => [6, "number", 2], + "approx" => [6, "number"], + ]; + + function testReader1() { + $inf = fopen("php://memory", "w+b"); + fwrite($inf, << "Clain", + "prenom" => "Jephte", + "date_nai" => "29/06/1975", + "exact" => 15.0, + "approx" => 153, + ], + [ + "nom" => "", + "prenom" => "Francoise", + "date_nai" => "29/05/1959", + "exact" => false, + "approx" => false, + ], + ], $result); + + rewind($inf); + $reader = new FsvReader($inf, self::SCHEMA); + $reader->setOutputSeq(true); + $result = iterator_to_array($reader); + self::assertSame([ + ["nom", "prenom", "date_nai", "exact", "approx"], + ["Clain", "Jephte", "29/06/1975", 15.0, 153], + ["", "Francoise", "29/05/1959", false, false], + ], $result); + } + + function testReader2() { + $inf = fopen(__DIR__."/latin1.fsv", "rb"); + + rewind($inf); + $reader = new FsvReader($inf, self::SCHEMA); + $result = iterator_to_array($reader); + self::assertSame([ + [ + "nom" => "Clain", + "prenom" => "Jephté", + "date_nai" => "29/06/1975", + "exact" => 15.0, + "approx" => 153, + ], + [ + "nom" => "", + "prenom" => "Francoise", + "date_nai" => "29/05/1959", + "exact" => false, + "approx" => false, + ], + ], $result); + + rewind($inf); + $reader = new FsvReader($inf, self::SCHEMA); + $reader->setOutputSeq(true); + $result = iterator_to_array($reader); + self::assertSame([ + ["nom", "prenom", "date_nai", "exact", "approx"], + ["Clain", "Jephté", "29/06/1975", 15.0, 153], + ["", "Francoise", "29/05/1959", false, false], + ], $result); + } +} diff --git a/nur_tests/mapper/fsv/FsvSchemaTest.php b/nur_tests/mapper/fsv/FsvSchemaTest.php new file mode 100644 index 0000000..8763d18 --- /dev/null +++ b/nur_tests/mapper/fsv/FsvSchemaTest.php @@ -0,0 +1,58 @@ + [10, "string"], + "prenom" => [10, "string"], + "date_nai" => [8, "date"], + "exact" => [6, "number", 2], + "approx" => [6, "number"], + ]; + + const LINE1 = "Clain Jephté 29061975001500000153"; + const ROW1 = [ + "nom" => "Clain", + "prenom" => "Jephté", + "date_nai" => "29/06/1975", + "exact" => 15.0, + "approx" => 153, + ]; + + const LINE2a = " Francoise 29051959"; + const LINE2b = " Francoise 29051959 "; + const ROW2 = [ + "nom" => "", + "prenom" => "Francoise", + "date_nai" => "29/05/1959", + "exact" => false, + "approx" => false, + ]; + + private static function latin1(string $line): string { + return iconv("utf-8", "latin1", $line); + } + private static function utf8(string $line): string { + return iconv("latin1", "utf-8", $line); + } + + function testParse() { + $schema = new FsvSchema(self::FSV_SCHEMA); + self::assertSame(self::ROW1 + , $schema->parseRow(self::latin1(self::LINE1))); + self::assertSame(self::ROW2 + , $schema->parseRow(self::latin1(self::LINE2a))); + self::assertSame(self::ROW2 + , $schema->parseRow(self::latin1(self::LINE2b))); + } + + function testFormat() { + $schema = new FsvSchema(self::FSV_SCHEMA); + self::assertSame(self::LINE1 + , self::utf8($schema->formatLine(self::ROW1))); + self::assertSame(self::LINE2b + , self::utf8($schema->formatLine(self::ROW2))); + } +} diff --git a/nur_tests/mapper/fsv/latin1.fsv b/nur_tests/mapper/fsv/latin1.fsv new file mode 100644 index 0000000..200c447 --- /dev/null +++ b/nur_tests/mapper/fsv/latin1.fsv @@ -0,0 +1,2 @@ +Clain Jephté 29061975001500000153 + Francoise 29051959 diff --git a/nur_tests/mapper/item/AssocMapperTest.php b/nur_tests/mapper/item/AssocMapperTest.php new file mode 100644 index 0000000..a576182 --- /dev/null +++ b/nur_tests/mapper/item/AssocMapperTest.php @@ -0,0 +1,52 @@ + "clain", "prenom" => "jephté", "age" => "45"], + ], + iterator_to_array($assoc) + ); + + $assoc = new Seq2AssocMapper(self::SOURCE); + $assoc->setParseKeys(false); + self::assertSame( + [ + ["nom", "prenom", "age"], + ["clain", "jephté", "45"], + ], + iterator_to_array($assoc) + ); + + $assoc = new Seq2AssocMapper(self::SOURCE); + $assoc->setParseKeys(false); + $assoc->setKeys(["nom", "prenom", "age"]); + self::assertSame( + [ + ["nom" => "nom", "prenom" => "prenom", "age" => "age"], + ["nom" => "clain", "prenom" => "jephté", "age" => "45"], + ], + iterator_to_array($assoc) + ); + + $assoc = new Seq2AssocMapper(self::SOURCE); + $assoc->setParseKeys(true); + $assoc->setKeys(["nom", "prenom", "age"]); + self::assertSame( + [ + ["nom" => "clain", "prenom" => "jephté", "age" => "45"], + ], + iterator_to_array($assoc) + ); + } +} diff --git a/nur_tests/mapper/item/AttributeFilterMapperTest.php b/nur_tests/mapper/item/AttributeFilterMapperTest.php new file mode 100644 index 0000000..3ee28e9 --- /dev/null +++ b/nur_tests/mapper/item/AttributeFilterMapperTest.php @@ -0,0 +1,39 @@ +result[] = $item; + } + }; + $consumer->consume(self::SOURCE, + Seq2AssocMapper::class, + [SchemaMapper::class, [ + "nom" => "string", + "prenom" => "string", + "age" => "int", + ]], + [ItemFilterMapper::class, "skip_items" => 1, "max_count" => 1], + [AttributeFilterMapper::class, [ + "nom", + "middle" => 5, + "age", + ]]); + self::assertSame([ + ["nom" => "clain", "middle" => 5, "age" => 53], + ], $consumer->result); + } +} diff --git a/nur_tests/mapper/item/ItemFilterMapperTest.php b/nur_tests/mapper/item/ItemFilterMapperTest.php new file mode 100644 index 0000000..d856ab9 --- /dev/null +++ b/nur_tests/mapper/item/ItemFilterMapperTest.php @@ -0,0 +1,24 @@ +setSkipItems(-1); + self::assertSame(self::SOURCE, iterator_to_array($filter)); + + $filter->setSkipItems(0); + self::assertSame(self::SOURCE, iterator_to_array($filter)); + + $filter->setSkipItems(1); + self::assertSame([2, 3, 4, 5], iterator_to_array($filter)); + + $filter->setSkipItems(2); + self::assertSame([3, 4, 5], iterator_to_array($filter)); + } +} diff --git a/sync-nur.sh b/sync-nur.sh index 0e6d9ab..4eedc13 100755 --- a/sync-nur.sh +++ b/sync-nur.sh @@ -16,6 +16,9 @@ parse_args "$@"; set -- "${args[@]}" cd "$MYDIR" +FROM=../nur-tests +sy src/ nur_src/t/ + FROM=../nur-base sy src_api/ nur_src/ for i in b cli config data io m php ref tools v; do @@ -39,3 +42,7 @@ sy src/ nur_src/m/oracle/ FROM=../nur-m-pgsql sy src/ nur_src/m/pgsql/ +FROM=../nur-mapper +sy src/ nur_src/mapper/ +sy tests/ nur_tests/mapper/ +sy tbin/ nur_tbin/