From 24a9d55ea0658c67478d0a94a9955de945743a11 Mon Sep 17 00:00:00 2001 From: Dmitry Borisenko <49808844+DmitryBorisenko33@users.noreply.github.com> Date: Wed, 22 Dec 2021 14:09:50 +0100 Subject: [PATCH] first --- .gitignore | 7 + .vscode/extensions.json | 7 + .vscode/settings.json | 5 + LICENSE | 21 + README.md | 6 + data_svelte/build/bundle.css | 14 + data_svelte/build/bundle.js | 2 + data_svelte/check.json | 0 data_svelte/config.json | 37 + data_svelte/favicon.png | Bin 0 -> 3127 bytes data_svelte/index.html | 17 + data_svelte/setup.json | 542 ++ data_svelte/sync.ffs_db | Bin 0 -> 188 bytes include/main.h | 14 + include/rest.h | 9 + lib/ArduinoStreamUtils/.clang-format | 5 + lib/ArduinoStreamUtils/.github/FUNDING.yml | 4 + .../.github/workflows/ci.yml | 121 + lib/ArduinoStreamUtils/.gitignore | 2 + lib/ArduinoStreamUtils/CHANGELOG.md | 79 + lib/ArduinoStreamUtils/CMakeLists.txt | 18 + lib/ArduinoStreamUtils/LICENSE.md | 10 + lib/ArduinoStreamUtils/README.md | 369 + .../examples/EepromRead/EepromRead.ino | 63 + .../examples/EepromWrite/EepromWrite.ino | 69 + .../HammingSerial1/HammingSerial1.ino | 68 + .../HammingSoftwareSerial.ino | 67 + .../examples/Logger/Logger.ino | 61 + .../examples/ReadBuffer/ReadBuffer.ino | 56 + .../examples/ReadLogger/ReadLogger.ino | 55 + .../examples/StringPrint/StringPrint.ino | 54 + .../examples/StringStream/StringStream.ino | 55 + .../examples/WriteBuffer/WriteBuffer.ino | 62 + .../examples/WriteLogger/WriteLogger.ino | 55 + .../extras/images/EepromStream.svg | 1 + .../extras/images/HammingDecodingStream.svg | 1 + .../extras/images/HammingEncodingStream.svg | 1 + .../extras/images/HammingStream.svg | 1 + .../extras/images/Logger.svg | 1 + .../extras/images/ReadBuffer.svg | 1 + .../extras/images/ReadLogger.svg | 1 + .../extras/images/StringPrint.svg | 1 + .../extras/images/StringStream.svg | 1 + .../extras/images/WriteBuffer.svg | 1 + .../extras/images/WriteLogger.svg | 1 + .../extras/images/WriteWaitingStream.svg | 1 + .../extras/test/BufferingPrintTest.cpp | 137 + .../extras/test/CMakeLists.txt | 104 + .../extras/test/EepromStreamTest.cpp | 57 + .../extras/test/FailingAllocator.hpp | 14 + .../extras/test/HammingClientTest.cpp | 30 + .../extras/test/HammingDecodingClientTest.cpp | 137 + .../extras/test/HammingDecodingStreamTest.cpp | 290 + .../extras/test/HammingEncodingClientTest.cpp | 30 + .../extras/test/HammingEncodingStreamTest.cpp | 30 + .../extras/test/HammingPrintTest.cpp | 202 + .../extras/test/HammingStreamTest.cpp | 30 + .../extras/test/LoggingClientTest.cpp | 138 + .../extras/test/LoggingPrintTest.cpp | 41 + .../extras/test/LoggingStreamTest.cpp | 93 + .../extras/test/MemoryStreamTest.cpp | 116 + .../extras/test/ReadBufferingClientTest.cpp | 337 + .../extras/test/ReadBufferingStreamTest.cpp | 318 + .../extras/test/ReadLoggingClientTest.cpp | 139 + .../extras/test/ReadLoggingStreamTest.cpp | 89 + .../extras/test/ReadThrottlingStreamTest.cpp | 89 + .../extras/test/SpyingAllocator.hpp | 36 + .../extras/test/StringPrintTest.cpp | 77 + .../extras/test/StringStreamTest.cpp | 116 + .../extras/test/WaitingPrintTest.cpp | 104 + .../extras/test/WriteBufferingClientTest.cpp | 203 + .../extras/test/WriteBufferingStreamTest.cpp | 166 + .../extras/test/WriteLoggingClientTest.cpp | 140 + .../extras/test/WriteLoggingStreamTest.cpp | 90 + .../extras/test/WriteWaitingClientTest.cpp | 104 + .../extras/test/WriteWaitingStreamTest.cpp | 104 + .../extras/test/cores/avr/Arduino.h | 18 + .../extras/test/cores/avr/CMakeLists.txt | 22 + .../extras/test/cores/avr/Client.h | 27 + .../extras/test/cores/avr/EEPROM.cpp | 12 + .../extras/test/cores/avr/EEPROM.h | 12 + .../extras/test/cores/avr/Print.h | 42 + .../extras/test/cores/avr/Stream.h | 41 + .../extras/test/cores/avr/WString.h | 43 + .../extras/test/cores/esp32/Arduino.h | 18 + .../extras/test/cores/esp32/CMakeLists.txt | 22 + .../extras/test/cores/esp32/Client.h | 26 + .../extras/test/cores/esp32/EEPROM.cpp | 20 + .../extras/test/cores/esp32/EEPROM.h | 12 + .../extras/test/cores/esp32/Print.h | 42 + .../extras/test/cores/esp32/Stream.h | 48 + .../extras/test/cores/esp32/WString.h | 5 + .../extras/test/cores/esp8266/Arduino.h | 18 + .../extras/test/cores/esp8266/CMakeLists.txt | 22 + .../extras/test/cores/esp8266/Client.h | 26 + .../extras/test/cores/esp8266/EEPROM.cpp | 19 + .../extras/test/cores/esp8266/EEPROM.h | 12 + .../extras/test/cores/esp8266/Print.h | 43 + .../extras/test/cores/esp8266/Stream.h | 45 + .../extras/test/cores/esp8266/WString.h | 5 + .../extras/test/cores/nrf52/Arduino.h | 18 + .../extras/test/cores/nrf52/CMakeLists.txt | 15 + .../extras/test/cores/nrf52/Client.h | 26 + .../extras/test/cores/nrf52/Print.h | 45 + .../extras/test/cores/nrf52/Stream.h | 45 + .../extras/test/cores/nrf52/WString.h | 5 + .../extras/test/cores/samd/Arduino.h | 18 + .../extras/test/cores/samd/CMakeLists.txt | 15 + .../extras/test/cores/samd/Client.h | 26 + .../extras/test/cores/samd/Print.h | 46 + .../extras/test/cores/samd/Stream.h | 44 + .../extras/test/cores/samd/WString.h | 5 + .../extras/test/cores/stm32/Arduino.h | 18 + .../extras/test/cores/stm32/CMakeLists.txt | 22 + .../extras/test/cores/stm32/Client.h | 28 + .../extras/test/cores/stm32/EEPROM.cpp | 12 + .../extras/test/cores/stm32/EEPROM.h | 11 + .../extras/test/cores/stm32/Print.h | 41 + .../extras/test/cores/stm32/Stream.h | 42 + .../extras/test/cores/stm32/WString.h | 43 + .../extras/test/cores/teensy/Arduino.h | 18 + .../extras/test/cores/teensy/CMakeLists.txt | 22 + .../extras/test/cores/teensy/Client.h | 31 + .../extras/test/cores/teensy/EEPROM.cpp | 12 + .../extras/test/cores/teensy/EEPROM.h | 12 + .../extras/test/cores/teensy/Print.h | 46 + .../extras/test/cores/teensy/Stream.h | 42 + .../extras/test/cores/teensy/WString.h | 7 + lib/ArduinoStreamUtils/extras/test/doctest.h | 6000 +++++++++++++++++ lib/ArduinoStreamUtils/extras/test/main.cpp | 6 + lib/ArduinoStreamUtils/keywords.txt | 13 + lib/ArduinoStreamUtils/library.properties | 11 + lib/ArduinoStreamUtils/src/StreamUtils.h | 7 + lib/ArduinoStreamUtils/src/StreamUtils.hpp | 28 + .../src/StreamUtils/Buffers/CharArray.hpp | 57 + .../StreamUtils/Buffers/CircularBuffer.hpp | 101 + .../src/StreamUtils/Buffers/LinearBuffer.hpp | 111 + .../src/StreamUtils/Clients/ClientProxy.hpp | 105 + .../src/StreamUtils/Clients/HammingClient.hpp | 23 + .../Clients/HammingDecodingClient.hpp | 24 + .../Clients/HammingEncodingClient.hpp | 24 + .../src/StreamUtils/Clients/LoggingClient.hpp | 23 + .../src/StreamUtils/Clients/MemoryClient.hpp | 92 + .../Clients/ReadBufferingClient.hpp | 30 + .../StreamUtils/Clients/ReadLoggingClient.hpp | 23 + .../src/StreamUtils/Clients/SpyingClient.hpp | 21 + .../Clients/WriteBufferingClient.hpp | 29 + .../Clients/WriteLoggingClient.hpp | 24 + .../Clients/WriteWaitingClient.hpp | 30 + .../src/StreamUtils/Configuration.hpp | 53 + .../src/StreamUtils/Helpers.hpp | 17 + .../Policies/ConnectForwardingPolicy.hpp | 35 + .../Policies/ConnectSpyingPolicy.hpp | 68 + .../Policies/HammingDecodingPolicy.hpp | 137 + .../Policies/HammingEncodingPolicy.hpp | 116 + .../Policies/ReadBufferingPolicy.hpp | 97 + .../Policies/ReadForwardingPolicy.hpp | 33 + .../Policies/ReadLoggingPolicy.hpp | 46 + .../StreamUtils/Policies/ReadSpyingPolicy.hpp | 64 + .../Policies/ReadThrottlingPolicy.hpp | 47 + .../Policies/WriteBufferingPolicy.hpp | 78 + .../Policies/WriteForwardingPolicy.hpp | 27 + .../Policies/WriteLoggingPolicy.hpp | 39 + .../Policies/WriteSpyingPolicy.hpp | 55 + .../Policies/WriteWaitingPolicy.hpp | 63 + .../src/StreamUtils/Polyfills.hpp | 60 + .../StreamUtils/Ports/ArduinoThrottler.hpp | 29 + .../StreamUtils/Ports/DefaultAllocator.hpp | 21 + .../src/StreamUtils/Prints/BufferingPrint.hpp | 22 + .../src/StreamUtils/Prints/HammingPrint.hpp | 19 + .../src/StreamUtils/Prints/LoggingPrint.hpp | 17 + .../src/StreamUtils/Prints/PrintProxy.hpp | 50 + .../src/StreamUtils/Prints/SpyingPrint.hpp | 17 + .../src/StreamUtils/Prints/StringPrint.hpp | 52 + .../src/StreamUtils/Prints/WaitingPrint.hpp | 22 + .../src/StreamUtils/Streams/EepromStream.hpp | 76 + .../Streams/HammingDecodingStream.hpp | 22 + .../Streams/HammingEncodingStream.hpp | 22 + .../src/StreamUtils/Streams/HammingStream.hpp | 21 + .../src/StreamUtils/Streams/LoggingStream.hpp | 19 + .../src/StreamUtils/Streams/MemoryStream.hpp | 61 + .../Streams/ReadBufferingStream.hpp | 28 + .../StreamUtils/Streams/ReadLoggingStream.hpp | 19 + .../Streams/ReadThrottlingStream.hpp | 35 + .../src/StreamUtils/Streams/SpyingStream.hpp | 19 + .../src/StreamUtils/Streams/StreamProxy.hpp | 68 + .../src/StreamUtils/Streams/StringStream.hpp | 77 + .../Streams/WriteBufferingStream.hpp | 24 + .../Streams/WriteLoggingStream.hpp | 20 + .../Streams/WriteWaitingStream.hpp | 26 + platformio.ini | 29 + src/main.cpp | 66 + src/rest.cpp | 87 + tools/littlefsbuilder.py | 2 + tools/mklittlefs.exe | Bin 0 -> 982528 bytes 195 files changed, 15629 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/extensions.json create mode 100644 .vscode/settings.json create mode 100644 LICENSE create mode 100644 README.md create mode 100644 data_svelte/build/bundle.css create mode 100644 data_svelte/build/bundle.js create mode 100644 data_svelte/check.json create mode 100644 data_svelte/config.json create mode 100644 data_svelte/favicon.png create mode 100644 data_svelte/index.html create mode 100644 data_svelte/setup.json create mode 100644 data_svelte/sync.ffs_db create mode 100644 include/main.h create mode 100644 include/rest.h create mode 100644 lib/ArduinoStreamUtils/.clang-format create mode 100644 lib/ArduinoStreamUtils/.github/FUNDING.yml create mode 100644 lib/ArduinoStreamUtils/.github/workflows/ci.yml create mode 100644 lib/ArduinoStreamUtils/.gitignore create mode 100644 lib/ArduinoStreamUtils/CHANGELOG.md create mode 100644 lib/ArduinoStreamUtils/CMakeLists.txt create mode 100644 lib/ArduinoStreamUtils/LICENSE.md create mode 100644 lib/ArduinoStreamUtils/README.md create mode 100644 lib/ArduinoStreamUtils/examples/EepromRead/EepromRead.ino create mode 100644 lib/ArduinoStreamUtils/examples/EepromWrite/EepromWrite.ino create mode 100644 lib/ArduinoStreamUtils/examples/HammingSerial1/HammingSerial1.ino create mode 100644 lib/ArduinoStreamUtils/examples/HammingSoftwareSerial/HammingSoftwareSerial.ino create mode 100644 lib/ArduinoStreamUtils/examples/Logger/Logger.ino create mode 100644 lib/ArduinoStreamUtils/examples/ReadBuffer/ReadBuffer.ino create mode 100644 lib/ArduinoStreamUtils/examples/ReadLogger/ReadLogger.ino create mode 100644 lib/ArduinoStreamUtils/examples/StringPrint/StringPrint.ino create mode 100644 lib/ArduinoStreamUtils/examples/StringStream/StringStream.ino create mode 100644 lib/ArduinoStreamUtils/examples/WriteBuffer/WriteBuffer.ino create mode 100644 lib/ArduinoStreamUtils/examples/WriteLogger/WriteLogger.ino create mode 100644 lib/ArduinoStreamUtils/extras/images/EepromStream.svg create mode 100644 lib/ArduinoStreamUtils/extras/images/HammingDecodingStream.svg create mode 100644 lib/ArduinoStreamUtils/extras/images/HammingEncodingStream.svg create mode 100644 lib/ArduinoStreamUtils/extras/images/HammingStream.svg create mode 100644 lib/ArduinoStreamUtils/extras/images/Logger.svg create mode 100644 lib/ArduinoStreamUtils/extras/images/ReadBuffer.svg create mode 100644 lib/ArduinoStreamUtils/extras/images/ReadLogger.svg create mode 100644 lib/ArduinoStreamUtils/extras/images/StringPrint.svg create mode 100644 lib/ArduinoStreamUtils/extras/images/StringStream.svg create mode 100644 lib/ArduinoStreamUtils/extras/images/WriteBuffer.svg create mode 100644 lib/ArduinoStreamUtils/extras/images/WriteLogger.svg create mode 100644 lib/ArduinoStreamUtils/extras/images/WriteWaitingStream.svg create mode 100644 lib/ArduinoStreamUtils/extras/test/BufferingPrintTest.cpp create mode 100644 lib/ArduinoStreamUtils/extras/test/CMakeLists.txt create mode 100644 lib/ArduinoStreamUtils/extras/test/EepromStreamTest.cpp create mode 100644 lib/ArduinoStreamUtils/extras/test/FailingAllocator.hpp create mode 100644 lib/ArduinoStreamUtils/extras/test/HammingClientTest.cpp create mode 100644 lib/ArduinoStreamUtils/extras/test/HammingDecodingClientTest.cpp create mode 100644 lib/ArduinoStreamUtils/extras/test/HammingDecodingStreamTest.cpp create mode 100644 lib/ArduinoStreamUtils/extras/test/HammingEncodingClientTest.cpp create mode 100644 lib/ArduinoStreamUtils/extras/test/HammingEncodingStreamTest.cpp create mode 100644 lib/ArduinoStreamUtils/extras/test/HammingPrintTest.cpp create mode 100644 lib/ArduinoStreamUtils/extras/test/HammingStreamTest.cpp create mode 100644 lib/ArduinoStreamUtils/extras/test/LoggingClientTest.cpp create mode 100644 lib/ArduinoStreamUtils/extras/test/LoggingPrintTest.cpp create mode 100644 lib/ArduinoStreamUtils/extras/test/LoggingStreamTest.cpp create mode 100644 lib/ArduinoStreamUtils/extras/test/MemoryStreamTest.cpp create mode 100644 lib/ArduinoStreamUtils/extras/test/ReadBufferingClientTest.cpp create mode 100644 lib/ArduinoStreamUtils/extras/test/ReadBufferingStreamTest.cpp create mode 100644 lib/ArduinoStreamUtils/extras/test/ReadLoggingClientTest.cpp create mode 100644 lib/ArduinoStreamUtils/extras/test/ReadLoggingStreamTest.cpp create mode 100644 lib/ArduinoStreamUtils/extras/test/ReadThrottlingStreamTest.cpp create mode 100644 lib/ArduinoStreamUtils/extras/test/SpyingAllocator.hpp create mode 100644 lib/ArduinoStreamUtils/extras/test/StringPrintTest.cpp create mode 100644 lib/ArduinoStreamUtils/extras/test/StringStreamTest.cpp create mode 100644 lib/ArduinoStreamUtils/extras/test/WaitingPrintTest.cpp create mode 100644 lib/ArduinoStreamUtils/extras/test/WriteBufferingClientTest.cpp create mode 100644 lib/ArduinoStreamUtils/extras/test/WriteBufferingStreamTest.cpp create mode 100644 lib/ArduinoStreamUtils/extras/test/WriteLoggingClientTest.cpp create mode 100644 lib/ArduinoStreamUtils/extras/test/WriteLoggingStreamTest.cpp create mode 100644 lib/ArduinoStreamUtils/extras/test/WriteWaitingClientTest.cpp create mode 100644 lib/ArduinoStreamUtils/extras/test/WriteWaitingStreamTest.cpp create mode 100644 lib/ArduinoStreamUtils/extras/test/cores/avr/Arduino.h create mode 100644 lib/ArduinoStreamUtils/extras/test/cores/avr/CMakeLists.txt create mode 100644 lib/ArduinoStreamUtils/extras/test/cores/avr/Client.h create mode 100644 lib/ArduinoStreamUtils/extras/test/cores/avr/EEPROM.cpp create mode 100644 lib/ArduinoStreamUtils/extras/test/cores/avr/EEPROM.h create mode 100644 lib/ArduinoStreamUtils/extras/test/cores/avr/Print.h create mode 100644 lib/ArduinoStreamUtils/extras/test/cores/avr/Stream.h create mode 100644 lib/ArduinoStreamUtils/extras/test/cores/avr/WString.h create mode 100644 lib/ArduinoStreamUtils/extras/test/cores/esp32/Arduino.h create mode 100644 lib/ArduinoStreamUtils/extras/test/cores/esp32/CMakeLists.txt create mode 100644 lib/ArduinoStreamUtils/extras/test/cores/esp32/Client.h create mode 100644 lib/ArduinoStreamUtils/extras/test/cores/esp32/EEPROM.cpp create mode 100644 lib/ArduinoStreamUtils/extras/test/cores/esp32/EEPROM.h create mode 100644 lib/ArduinoStreamUtils/extras/test/cores/esp32/Print.h create mode 100644 lib/ArduinoStreamUtils/extras/test/cores/esp32/Stream.h create mode 100644 lib/ArduinoStreamUtils/extras/test/cores/esp32/WString.h create mode 100644 lib/ArduinoStreamUtils/extras/test/cores/esp8266/Arduino.h create mode 100644 lib/ArduinoStreamUtils/extras/test/cores/esp8266/CMakeLists.txt create mode 100644 lib/ArduinoStreamUtils/extras/test/cores/esp8266/Client.h create mode 100644 lib/ArduinoStreamUtils/extras/test/cores/esp8266/EEPROM.cpp create mode 100644 lib/ArduinoStreamUtils/extras/test/cores/esp8266/EEPROM.h create mode 100644 lib/ArduinoStreamUtils/extras/test/cores/esp8266/Print.h create mode 100644 lib/ArduinoStreamUtils/extras/test/cores/esp8266/Stream.h create mode 100644 lib/ArduinoStreamUtils/extras/test/cores/esp8266/WString.h create mode 100644 lib/ArduinoStreamUtils/extras/test/cores/nrf52/Arduino.h create mode 100644 lib/ArduinoStreamUtils/extras/test/cores/nrf52/CMakeLists.txt create mode 100644 lib/ArduinoStreamUtils/extras/test/cores/nrf52/Client.h create mode 100644 lib/ArduinoStreamUtils/extras/test/cores/nrf52/Print.h create mode 100644 lib/ArduinoStreamUtils/extras/test/cores/nrf52/Stream.h create mode 100644 lib/ArduinoStreamUtils/extras/test/cores/nrf52/WString.h create mode 100644 lib/ArduinoStreamUtils/extras/test/cores/samd/Arduino.h create mode 100644 lib/ArduinoStreamUtils/extras/test/cores/samd/CMakeLists.txt create mode 100644 lib/ArduinoStreamUtils/extras/test/cores/samd/Client.h create mode 100644 lib/ArduinoStreamUtils/extras/test/cores/samd/Print.h create mode 100644 lib/ArduinoStreamUtils/extras/test/cores/samd/Stream.h create mode 100644 lib/ArduinoStreamUtils/extras/test/cores/samd/WString.h create mode 100644 lib/ArduinoStreamUtils/extras/test/cores/stm32/Arduino.h create mode 100644 lib/ArduinoStreamUtils/extras/test/cores/stm32/CMakeLists.txt create mode 100644 lib/ArduinoStreamUtils/extras/test/cores/stm32/Client.h create mode 100644 lib/ArduinoStreamUtils/extras/test/cores/stm32/EEPROM.cpp create mode 100644 lib/ArduinoStreamUtils/extras/test/cores/stm32/EEPROM.h create mode 100644 lib/ArduinoStreamUtils/extras/test/cores/stm32/Print.h create mode 100644 lib/ArduinoStreamUtils/extras/test/cores/stm32/Stream.h create mode 100644 lib/ArduinoStreamUtils/extras/test/cores/stm32/WString.h create mode 100644 lib/ArduinoStreamUtils/extras/test/cores/teensy/Arduino.h create mode 100644 lib/ArduinoStreamUtils/extras/test/cores/teensy/CMakeLists.txt create mode 100644 lib/ArduinoStreamUtils/extras/test/cores/teensy/Client.h create mode 100644 lib/ArduinoStreamUtils/extras/test/cores/teensy/EEPROM.cpp create mode 100644 lib/ArduinoStreamUtils/extras/test/cores/teensy/EEPROM.h create mode 100644 lib/ArduinoStreamUtils/extras/test/cores/teensy/Print.h create mode 100644 lib/ArduinoStreamUtils/extras/test/cores/teensy/Stream.h create mode 100644 lib/ArduinoStreamUtils/extras/test/cores/teensy/WString.h create mode 100644 lib/ArduinoStreamUtils/extras/test/doctest.h create mode 100644 lib/ArduinoStreamUtils/extras/test/main.cpp create mode 100644 lib/ArduinoStreamUtils/keywords.txt create mode 100644 lib/ArduinoStreamUtils/library.properties create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils.h create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Buffers/CharArray.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Buffers/CircularBuffer.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Buffers/LinearBuffer.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Clients/ClientProxy.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Clients/HammingClient.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Clients/HammingDecodingClient.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Clients/HammingEncodingClient.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Clients/LoggingClient.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Clients/MemoryClient.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Clients/ReadBufferingClient.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Clients/ReadLoggingClient.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Clients/SpyingClient.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Clients/WriteBufferingClient.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Clients/WriteLoggingClient.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Clients/WriteWaitingClient.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Configuration.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Helpers.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Policies/ConnectForwardingPolicy.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Policies/ConnectSpyingPolicy.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Policies/HammingDecodingPolicy.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Policies/HammingEncodingPolicy.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Policies/ReadBufferingPolicy.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Policies/ReadForwardingPolicy.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Policies/ReadLoggingPolicy.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Policies/ReadSpyingPolicy.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Policies/ReadThrottlingPolicy.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Policies/WriteBufferingPolicy.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Policies/WriteForwardingPolicy.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Policies/WriteLoggingPolicy.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Policies/WriteSpyingPolicy.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Policies/WriteWaitingPolicy.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Polyfills.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Ports/ArduinoThrottler.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Ports/DefaultAllocator.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Prints/BufferingPrint.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Prints/HammingPrint.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Prints/LoggingPrint.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Prints/PrintProxy.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Prints/SpyingPrint.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Prints/StringPrint.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Prints/WaitingPrint.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Streams/EepromStream.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Streams/HammingDecodingStream.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Streams/HammingEncodingStream.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Streams/HammingStream.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Streams/LoggingStream.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Streams/MemoryStream.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Streams/ReadBufferingStream.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Streams/ReadLoggingStream.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Streams/ReadThrottlingStream.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Streams/SpyingStream.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Streams/StreamProxy.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Streams/StringStream.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Streams/WriteBufferingStream.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Streams/WriteLoggingStream.hpp create mode 100644 lib/ArduinoStreamUtils/src/StreamUtils/Streams/WriteWaitingStream.hpp create mode 100644 platformio.ini create mode 100644 src/main.cpp create mode 100644 src/rest.cpp create mode 100644 tools/littlefsbuilder.py create mode 100644 tools/mklittlefs.exe diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..65469d13 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch +lib/libraies-master + diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..e80666bf --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..4e5b2b6f --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "C_Cpp.clang_format_fallbackStyle": "{BasedOnStyle: Google, IndentWidth: 4, ColumnLimit: 0}", + "C_Cpp.formatting": "clangFormat", + "C_Cpp.default.forcedInclude": [] +} \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..f8f7d0eb --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Dmitry Borisenko + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 00000000..99208207 --- /dev/null +++ b/README.md @@ -0,0 +1,6 @@ +# IoTManager +Это модульная система беспроводной автоматизации на базе ESP32/ESP8266 микроконтроллеров и приложения IoT Manager. +Телеграм канал обсуждения приложения и системы автоматизации https://t.me/IoTmanager +# [Инструкция](https://github.com/IoTManagerProject/IoTManager/wiki) + +![](https://github.com/IoTManagerProject/IoTManager/blob/beta/doc/pictures/007%20iot%20manager.jpg) diff --git a/data_svelte/build/bundle.css b/data_svelte/build/bundle.css new file mode 100644 index 00000000..7f28816d --- /dev/null +++ b/data_svelte/build/bundle.css @@ -0,0 +1,14 @@ +*,::before,::after{box-sizing:border-box}html{-moz-tab-size:4;-o-tab-size:4;tab-size:4}html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}body{font-family:system-ui, + -apple-system, /* Firefox supports this but not yet `system-ui` */ + 'Segoe UI', + Roboto, + Helvetica, + Arial, + sans-serif, + 'Apple Color Emoji', + 'Segoe UI Emoji'}hr{height:0;color:inherit}abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace, + SFMono-Regular, + Consolas, + 'Liberation Mono', + Menlo, + monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}table{text-indent:0;border-color:inherit}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,select{text-transform:none}button,[type='button']{-webkit-appearance:button}legend{padding:0}progress{vertical-align:baseline}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}button{background-color:transparent;background-image:none}fieldset{margin:0;padding:0}ol,ul{list-style:none;margin:0;padding:0}html{font-family:ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";line-height:1.5}body{font-family:inherit;line-height:inherit}*,::before,::after{box-sizing:border-box;border-width:0;border-style:solid;border-color:currentColor}hr{border-top-width:1px}img{border-style:solid}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input:-ms-input-placeholder,textarea:-ms-input-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button{cursor:pointer}table{border-collapse:collapse}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}button,input,optgroup,select,textarea{padding:0;line-height:inherit;color:inherit}pre,code,kbd,samp{font-family:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}*,::before,::after{--tw-border-opacity:1;border-color:rgba(229, 231, 235, var(--tw-border-opacity))}.container{width:100%}@media(min-width: 640px){.container{max-width:640px}}@media(min-width: 768px){.container{max-width:768px}}@media(min-width: 1024px){.container{max-width:1024px}}@media(min-width: 1280px){.container{max-width:1280px}}@media(min-width: 1536px){.container{max-width:1536px}}.btn-indigo{border-radius:0.5rem;--tw-bg-opacity:1;background-color:rgba(99, 102, 241, var(--tw-bg-opacity))}.btn-indigo:hover{--tw-bg-opacity:1;background-color:rgba(67, 56, 202, var(--tw-bg-opacity))}.btn-indigo{padding-left:1rem;padding-right:1rem;padding-top:0.5rem;padding-bottom:0.5rem;font-weight:600;--tw-text-opacity:1;color:rgba(255, 255, 255, var(--tw-text-opacity));--tw-shadow:0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow)}.btn-indigo:focus{outline:2px solid transparent;outline-offset:2px;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);--tw-ring-opacity:1;--tw-ring-color:rgba(129, 140, 248, var(--tw-ring-opacity));--tw-ring-opacity:0.75}.widget-input-indigo{width:100%;-webkit-appearance:none;-moz-appearance:none;appearance:none;align-content:center;border-radius:0.25rem;border-width:2px;--tw-border-opacity:1;border-color:rgba(229, 231, 235, var(--tw-border-opacity))}.widget-input-indigo:focus{--tw-border-opacity:1;border-color:rgba(99, 102, 241, var(--tw-border-opacity))}.widget-input-indigo{--tw-bg-opacity:1;background-color:rgba(229, 231, 235, var(--tw-bg-opacity))}.widget-input-indigo:focus{--tw-bg-opacity:1;background-color:rgba(255, 255, 255, var(--tw-bg-opacity))}.widget-input-indigo{padding-top:0.25rem;padding-bottom:0.25rem;padding-right:1rem;line-height:1.25;--tw-text-opacity:1;color:rgba(55, 65, 81, var(--tw-text-opacity))}.widget-input-indigo:focus{outline:2px solid transparent;outline-offset:2px}.cards-grid{display:grid;grid-template-columns:repeat(1, minmax(0, 1fr));justify-items:center}@media(min-width: 640px){.cards-grid{grid-template-columns:repeat(2, minmax(0, 1fr))}}@media(min-width: 1024px){.cards-grid{grid-template-columns:repeat(3, minmax(0, 1fr))}}@media(min-width: 1280px){.cards-grid{grid-template-columns:repeat(3, minmax(0, 1fr))}}@media(min-width: 1536px){.cards-grid{grid-template-columns:repeat(3, minmax(0, 1fr))}}.card{margin-top:0.5rem;margin-bottom:0.5rem;width:91.666667%;border-radius:0.5rem;--tw-bg-opacity:1;background-color:rgba(255, 255, 255, var(--tw-bg-opacity));padding:0.5rem;--tw-shadow:0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow)}@media(min-width: 640px){.card{padding:0.5rem}}@media(min-width: 768px){.card{padding:0.5rem}}@media(min-width: 1024px){.card{padding:0.5rem;--tw-shadow:0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow)}}@media(min-width: 1280px){.card{padding-left:2rem;padding-right:2rem;padding-top:1rem;padding-bottom:1rem}}@media(min-width: 1536px){.card{padding-left:2rem;padding-right:2rem;padding-top:1rem;padding-bottom:1rem}}.card-title-gray{padding-bottom:1.5rem;text-align:center;font-size:1.125rem;line-height:1.75rem;font-weight:700;--tw-text-opacity:1;color:rgba(107, 114, 128, var(--tw-text-opacity))}.card-items{margin-bottom:1.5rem}@media(min-width: 768px){.card-items{display:flex;align-items:center}}.widget-descr-gray-left{display:block;align-content:center;padding-right:1rem;font-weight:700;--tw-text-opacity:1;color:rgba(107, 114, 128, var(--tw-text-opacity))}@media(min-width: 768px){.widget-descr-gray-left{text-align:left}}.fixed{position:fixed}.m-0{margin:0px}.mx-2{margin-left:0.5rem;margin-right:0.5rem}.my-2{margin-top:0.5rem;margin-bottom:0.5rem}.mr-2{margin-right:0.5rem}.block{display:block}.table{display:table}.grid{display:grid}.h-10{height:2.5rem}.w-11\/12{width:91.666667%}.w-full{width:100%}.transform{--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;transform:translateX(var(--tw-translate-x)) translateY(var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@-webkit-keyframes spin{to{transform:rotate(360deg)}}@keyframes spin{to{transform:rotate(360deg)}}@-webkit-keyframes ping{75%,100%{transform:scale(2);opacity:0}}@keyframes ping{75%,100%{transform:scale(2);opacity:0}}@-webkit-keyframes pulse{50%{opacity:.5}}@keyframes pulse{50%{opacity:.5}}@-webkit-keyframes bounce{0%,100%{transform:translateY(-25%);-webkit-animation-timing-function:cubic-bezier(0.8,0,1,1);animation-timing-function:cubic-bezier(0.8,0,1,1)}50%{transform:none;-webkit-animation-timing-function:cubic-bezier(0,0,0.2,1);animation-timing-function:cubic-bezier(0,0,0.2,1)}}@keyframes bounce{0%,100%{transform:translateY(-25%);-webkit-animation-timing-function:cubic-bezier(0.8,0,1,1);animation-timing-function:cubic-bezier(0.8,0,1,1)}50%{transform:none;-webkit-animation-timing-function:cubic-bezier(0,0,0.2,1);animation-timing-function:cubic-bezier(0,0,0.2,1)}}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-cols-1{grid-template-columns:repeat(1, minmax(0, 1fr))}.place-items-center{place-items:center}.content-center{align-content:center}.rounded{border-radius:0.25rem}.rounded-lg{border-radius:0.5rem}.border-2{border-width:2px}.border-gray-200{--tw-border-opacity:1;border-color:rgba(229, 231, 235, var(--tw-border-opacity))}.bg-white{--tw-bg-opacity:1;background-color:rgba(255, 255, 255, var(--tw-bg-opacity))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgba(249, 250, 251, var(--tw-bg-opacity))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgba(243, 244, 246, var(--tw-bg-opacity))}.bg-gray-200{--tw-bg-opacity:1;background-color:rgba(229, 231, 235, var(--tw-bg-opacity))}.bg-indigo-500{--tw-bg-opacity:1;background-color:rgba(99, 102, 241, var(--tw-bg-opacity))}.hover\:bg-indigo-700:hover{--tw-bg-opacity:1;background-color:rgba(67, 56, 202, var(--tw-bg-opacity))}.focus\:bg-white:focus{--tw-bg-opacity:1;background-color:rgba(255, 255, 255, var(--tw-bg-opacity))}.bg-cover{background-size:cover}.p-2{padding:0.5rem}.px-4{padding-left:1rem;padding-right:1rem}.py-1{padding-top:0.25rem;padding-bottom:0.25rem}.py-2{padding-top:0.5rem;padding-bottom:0.5rem}.pt-16{padding-top:4rem}.pr-4{padding-right:1rem}.text-center{text-align:center}.text-right{text-align:right}.text-sm{font-size:0.875rem;line-height:1.25rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.font-semibold{font-weight:600}.font-bold{font-weight:700}.leading-tight{line-height:1.25}.text-white{--tw-text-opacity:1;color:rgba(255, 255, 255, var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity:1;color:rgba(107, 114, 128, var(--tw-text-opacity))}.text-gray-700{--tw-text-opacity:1;color:rgba(55, 65, 81, var(--tw-text-opacity))}*,::before,::after{--tw-shadow:0 0 #0000}.shadow-md{--tw-shadow:0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow)}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}*,::before,::after{--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59, 130, 246, 0.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000}.focus\:ring-2:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000)}.focus\:ring-indigo-400:focus{--tw-ring-opacity:1;--tw-ring-color:rgba(129, 140, 248, var(--tw-ring-opacity))}#menu__toggle{opacity:0}#menu__toggle:checked~.menu__btn>span{transform:rotate(45deg)}#menu__toggle:checked~.menu__btn>span::before{top:0;transform:rotate(0)}#menu__toggle:checked~.menu__btn>span::after{top:0;transform:rotate(90deg)}#menu__toggle:checked~.menu__box{visibility:visible;left:0}#menu__toggle:checked~.menu__main{margin-left:150px;transition-duration:0.25s}.menu__btn{display:flex;align-items:center;position:fixed;z-index:1;top:10px;left:20px;width:20px;height:20px;cursor:pointer}.menu__btn>span,.menu__btn>span::before,.menu__btn>span::after{display:block;position:absolute;width:100%;height:2px;background-color:#616161;transition-duration:0.25s}.menu__btn>span::before{content:"";top:-8px}.menu__btn>span::after{content:"";top:8px}.menu__box{display:block;position:fixed;visibility:hidden;top:0;left:-100%;width:150px;height:100%;margin:0;padding:80px 0;list-style:none;background-color:#eceff1;box-shadow:1px 0px 6px rgba(0, 0, 0, 0.2);transition-duration:0.25s}.menu__ham{position:fixed}.menu__item{display:block;padding:12px 24px;color:rgba(51, 51, 51, 0.788);font-family:"Roboto", sans-serif;font-size:15px;font-weight:600;text-decoration:none;transition-duration:0.25s}.menu__item:hover{background-color:#cfd8dc}.upper__bar{background-color:rgba(51, 51, 51, 0.144);height:70px;position:fixed;z-index:-1;top:0px;left:0;width:100%;margin:0;padding:0;box-shadow:1px 0px 3px rgba(0, 0, 0, 0.2)}input[type="date"]::-webkit-calendar-picker-indicator{margin-left:5px;margin-right:-8px}input[type="time"]::-webkit-calendar-picker-indicator{margin-left:5px;margin-right:-8px}input[type="number"]::-webkit-outer-spin-button,input[type="number"]::-webkit-inner-spin-button{margin-left:7px;margin-right:-6px;width:30px;height:30px;opacity:1}@media(min-width: 640px){.sm\:w-8\/12{width:66.666667%}.sm\:grid-cols-2{grid-template-columns:repeat(2, minmax(0, 1fr))}.sm\:p-2{padding:0.5rem}}@media(min-width: 768px){.md\:flex{display:flex}.md\:w-1\/3{width:33.333333%}.md\:w-2\/3{width:66.666667%}.md\:w-6\/12{width:50%}.md\:items-center{align-items:center}.md\:p-2{padding:0.5rem}}@media(min-width: 1024px){.lg\:w-1\/3{width:33.333333%}.lg\:w-2\/3{width:66.666667%}.lg\:w-5\/12{width:41.666667%}.lg\:grid-cols-3{grid-template-columns:repeat(3, minmax(0, 1fr))}.lg\:p-2{padding:0.5rem}}@media(min-width: 1280px){.xl\:grid-cols-3{grid-template-columns:repeat(3, minmax(0, 1fr))}.xl\:px-8{padding-left:2rem;padding-right:2rem}.xl\:py-4{padding-top:1rem;padding-bottom:1rem}}@media(min-width: 1536px){.\32xl\:w-1\/3{width:33.333333%}.\32xl\:w-2\/3{width:66.666667%}.\32xl\:w-4\/12{width:33.333333%}.\32xl\:grid-cols-3{grid-template-columns:repeat(3, minmax(0, 1fr))}.\32xl\:px-8{padding-left:2rem;padding-right:2rem}.\32xl\:py-4{padding-top:1rem;padding-bottom:1rem}} \ No newline at end of file diff --git a/data_svelte/build/bundle.js b/data_svelte/build/bundle.js new file mode 100644 index 00000000..4398b6b1 --- /dev/null +++ b/data_svelte/build/bundle.js @@ -0,0 +1,2 @@ +var app=function(){"use strict";function t(){}function e(t){return t()}function n(){return Object.create(null)}function r(t){t.forEach(e)}function c(t){return"function"==typeof t}function i(t,e){return t!=t?e==e:t!==e||t&&"object"==typeof t||"function"==typeof t}function o(t,e,n,r){if(t){const c=a(t,e,n,r);return t[0](c)}}function a(t,e,n,r){return t[1]&&r?function(t,e){for(const n in e)t[n]=e[n];return t}(n.ctx.slice(),t[1](r(e))):n.ctx}function l(t,e,n,r){if(t[2]&&r){const c=t[2](r(n));if(void 0===e.dirty)return c;if("object"==typeof c){const t=[],n=Math.max(e.dirty.length,c.length);for(let r=0;r32){const e=[],n=t.ctx.length/32;for(let t=0;tt.removeEventListener(e,n,r)}function y(t,e,n){null==n?t.removeAttribute(e):t.getAttribute(e)!==n&&t.setAttribute(e,n)}function x(t){return""===t?null:+t}function v(t,e){e=""+e,t.wholeText!==e&&(t.data=e)}function _(t,e){t.value=null==e?"":e}let k;function S(t){k=t}function E(){if(!k)throw new Error("Function called outside component initialization");return k}function O(t){E().$$.on_mount.push(t)}function H(t){return E().$$.context.get(t)}const N=[],A=[],M=[],C=[],R=Promise.resolve();let z=!1;function T(){z||(z=!0,R.then(j))}function q(){return T(),R}function I(t){M.push(t)}function F(t){C.push(t)}let L=!1;const W=new Set;function j(){if(!L){L=!0;do{for(let t=0;t{J.delete(t),r&&(n&&t.d(1),r())})),t.o(e)}}function G(t,e,n){const r=t.$$.props[e];void 0!==r&&(t.$$.bound[r]=n,n(t.$$.ctx[r]))}function Q(t){t&&t.c()}function U(t,n,i,o){const{fragment:a,on_mount:l,on_destroy:s,after_update:u}=t.$$;a&&a.m(n,i),o||I((()=>{const n=l.map(e).filter(c);s?s.push(...n):r(n),t.$$.on_mount=[]})),u.forEach(I)}function V(t,e){const n=t.$$;null!==n.fragment&&(r(n.on_destroy),n.fragment&&n.fragment.d(e),n.on_destroy=n.fragment=null,n.ctx=[])}function X(e,c,i,o,a,l,s,u=[-1]){const p=k;S(e);const f=e.$$={fragment:null,ctx:null,props:l,update:t,not_equal:a,bound:n(),on_mount:[],on_destroy:[],on_disconnect:[],before_update:[],after_update:[],context:new Map(p?p.$$.context:c.context||[]),callbacks:n(),dirty:u,skip_bound:!1,root:c.target||p.$$.root};s&&s(f.root);let m=!1;if(f.ctx=i?i(e,c.props||{},((t,n,...r)=>{const c=r.length?r[0]:n;return f.ctx&&a(f.ctx[t],f.ctx[t]=c)&&(!f.skip_bound&&f.bound[t]&&f.bound[t](c),m&&function(t,e){-1===t.$$.dirty[0]&&(N.push(t),T(),t.$$.dirty.fill(0)),t.$$.dirty[e/31|0]|=1<{const t=n.indexOf(e);-1!==t&&n.splice(t,1)}}$set(t){var e;this.$$set&&(e=t,0!==Object.keys(e).length)&&(this.$$.skip_bound=!0,this.$$set(t),this.$$.skip_bound=!1)}}const et=[];function nt(e,n=t){let r;const c=new Set;function o(t){if(i(e,t)&&(e=t,r)){const t=!et.length;for(const t of c)t[1](),et.push(t,e);if(t){for(let t=0;t{c.delete(l),0===c.size&&(r(),r=null)}}}}function rt(t,e=!1){return(t=t.slice(t.startsWith("/#")?2:0,t.endsWith("/*")?-2:void 0)).startsWith("/")||(t="/"+t),"/"===t&&(t=""),e&&!t.endsWith("/")&&(t+="/"),t}function ct(t,e,n){if(""===n)return t;if("/"===n[0])return n;let r=t=>t.split("/").filter((t=>""!==t)),c=r(t);return"/"+(e?r(e):[]).map(((t,e)=>c[e])).join("/")+"/"+n}function it(t,e,n,r){let c=[e,"data-"+e].reduce(((e,r)=>{let c=t.getAttribute(r);return n&&t.removeAttribute(r),null===c?e:c}),!1);return!r&&""===c||(c||r||!1)}function ot(t){let e=t.split("&").map((t=>t.split("="))).reduce(((t,e)=>{let n=e[0];if(!n)return t;let r=!(e.length>1)||e[e.length-1];return"string"==typeof r&&r.includes(",")&&(r=r.split(",")),void 0===t[n]?t[n]=[r]:t[n].push(r),t}),{});return Object.entries(e).reduce(((t,e)=>(t[e[0]]=e[1].length>1?e[1]:e[1][0],t)),{})}var at,lt,st={HISTORY:1,HASH:2,MEMORY:3,OFF:4,run:function(t,e,n,r){return 1===t?e&&e():2===t?n&&n():r&&r()},getDeafault:function(){return window&&"srcdoc"!==window.location.pathname?1:3}},ut=function(){let t,e=st.getDeafault(),n=n=>t&&t(pt(e));function r(t){t&&(e=t),window.onhashchange=window.onpopstate=lt=null,e!==st.OFF&&st.run(e,(t=>window.onpopstate=n),(t=>window.onhashchange=n))&&n()}return{mode:t=>r(t),get:t=>pt(e),go(t,r){(function(t,e,n){let r=t=>history[n?"replaceState":"pushState"]({},"",t);st.run(t,(t=>r(e)),(t=>r(`#${e}`)),(t=>lt=e))})(e,t,r),n()},start(e){t=e,r()},stop(){t=null,r(st.OFF)}}}();function pt(t){let e=at,n=at=st.run(t,(t=>window.location.pathname+window.location.search),(t=>String(window.location.hash.slice(1)||"/")),(t=>lt||"/")),r=n.match(/^([^?#]+)(?:\?([^#]+))?(?:\#(.+))?$/);return{url:n,from:e,path:r[1]||"",query:ot(r[2]||""),hash:r[3]||""}}function ft(t){let e=H("tinro");e&&(e.exact||e.fallback)&&function(t){throw new Error("[Tinro] "+t)}(`${t.fallback?"":``} can't be inside ${e.fallback?"":` with exact path`}`);let n=t.fallback?"fallbacks":"childs",r=nt({}),c={router:{},exact:!1,pattern:null,meta:{},parent:e,fallback:t.fallback,redirect:!1,firstmatch:!1,breadcrumb:null,matched:!1,childs:new Set,activeChilds:new Set,fallbacks:new Set,update(t){c.exact=!t.path.endsWith("/*"),c.pattern=rt(`${c.parent&&c.parent.pattern||""}${t.path}`),c.redirect=t.redirect,c.firstmatch=t.firstmatch,c.breadcrumb=t.breadcrumb,c.match()},register:()=>{if(c.parent)return c.parent[n].add(c),()=>{c.parent[n].delete(c),c.router.un&&c.router.un()}},show:()=>{t.onShow(),!c.fallback&&c.parent&&c.parent.activeChilds.add(c)},hide:()=>{t.onHide(),!c.fallback&&c.parent&&c.parent.activeChilds.delete(c)},match:async()=>{c.matched=!1;let{path:e,url:n,from:i,query:o}=c.router,a=function(t,e){t=rt(t,!0),e=rt(e,!0);let n=[],r={},c=!0,i=t.split("/").map((t=>t.startsWith(":")?(n.push(t.slice(1)),"([^\\/]+)"):t)).join("\\/"),o=e.match(new RegExp(`^${i}$`));return o||(c=!1,o=e.match(new RegExp(`^${i}`))),o?(n.forEach(((t,e)=>r[t]=o[e+1])),{exact:c,params:r,part:o[0].slice(0,-1)}):null}(c.pattern,e);if(!c.fallback&&a&&c.redirect&&(!c.exact||c.exact&&a.exact)){await q();let t=ct(e,c.parent&&c.parent.pattern,c.redirect);return mt.goto(t,!0)}if(c.meta=a&&{from:i,url:n,query:o,match:a.part,pattern:c.pattern,breadcrumbs:c.parent&&c.parent.meta&&c.parent.meta.breadcrumbs.slice()||[],params:a.params,subscribe:r.subscribe},c.breadcrumb&&c.meta&&c.meta.breadcrumbs.push({name:c.breadcrumb,path:a.part}),r.set(c.meta),!a||c.fallback||!(!c.exact||c.exact&&a.exact)||c.parent&&c.parent.firstmatch&&c.parent.matched?c.hide():(t.onMeta(c.meta),c.parent&&(c.parent.matched=!0),c.show()),await q(),a&&!c.fallback&&(c.childs.size>0&&0==c.activeChilds.size||0==c.childs.size&&c.fallbacks.size>0)){let t=c;for(;0==t.fallbacks.size;)if(t=t.parent,!t)return;t&&t.fallbacks.forEach((t=>{if(t.redirect){let e=ct("/",t.parent&&t.parent.pattern,t.redirect);mt.goto(e,!0)}else t.show()}))}}};return i="tinro",o=c,E().$$.context.set(i,o),O((()=>c.register())),c.router.un=mt.subscribe((t=>{c.router.path=t.path,c.router.url=t.url,c.router.query=t.query,c.router.from=t.from,null!==c.pattern&&c.match()})),c;var i,o}function dt(){return H("tinro").meta}var mt=function(){let{subscribe:t}=nt(ut.get(),(t=>{ut.start(t);let e=function(t){let e=e=>{let n=e.target.closest("a[href]"),r=n&&it(n,"target",!1,"_self"),c=n&&it(n,"tinro-ignore"),i=e.ctrlKey||e.metaKey||e.altKey||e.shiftKey;if("_self"==r&&!c&&!i&&n){let r=n.getAttribute("href").replace(/^\/#/,"");/^\/\/|^[a-zA-Z]+:/.test(r)||(e.preventDefault(),t(r.startsWith("/")?r:n.href.replace(window.location.origin,"")))}};return addEventListener("click",e),()=>removeEventListener("click",e)}(ut.go);return()=>{ut.stop(),e()}}));return{subscribe:t,goto:ut.go,params:$t,meta:dt,useHashNavigation:t=>ut.mode(t?st.HASH:st.HISTORY),mode:{hash:()=>ut.mode(st.HASH),history:()=>ut.mode(st.HISTORY),memory:()=>ut.mode(st.MEMORY)}}}();function $t(){return H("tinro").meta.params}const ht=t=>({params:2&t,meta:4&t}),gt=t=>({params:t[1],meta:t[2]});function bt(t){let e;const n=t[9].default,r=o(n,t,t[8],gt);return{c(){r&&r.c()},m(t,n){r&&r.m(t,n),e=!0},p(t,c){r&&r.p&&(!e||262&c)&&s(r,n,t,t[8],e?l(n,t[8],c,ht):u(t[8]),gt)},i(t){e||(P(r,t),e=!0)},o(t){Z(r,t),e=!1},d(t){r&&r.d(t)}}}function wt(t){let e,n,r=t[0]&&bt(t);return{c(){r&&r.c(),e=b()},m(t,c){r&&r.m(t,c),f(t,e,c),n=!0},p(t,[n]){t[0]?r?(r.p(t,n),1&n&&P(r,1)):(r=bt(t),r.c(),P(r,1),r.m(e.parentNode,e)):r&&(D(),Z(r,1,1,(()=>{r=null})),K())},i(t){n||(P(r),n=!0)},o(t){Z(r),n=!1},d(t){r&&r.d(t),t&&d(e)}}}function yt(t,e,n){let{$$slots:r={},$$scope:c}=e,{path:i="/*"}=e,{fallback:o=!1}=e,{redirect:a=!1}=e,{firstmatch:l=!1}=e,{breadcrumb:s=null}=e,u=!1,p={},f={};const d=ft({fallback:o,onShow(){n(0,u=!0)},onHide(){n(0,u=!1)},onMeta(t){n(2,f=t),n(1,p=f.params)}});return t.$$set=t=>{"path"in t&&n(3,i=t.path),"fallback"in t&&n(4,o=t.fallback),"redirect"in t&&n(5,a=t.redirect),"firstmatch"in t&&n(6,l=t.firstmatch),"breadcrumb"in t&&n(7,s=t.breadcrumb),"$$scope"in t&&n(8,c=t.$$scope)},t.$$.update=()=>{232&t.$$.dirty&&d.update({path:i,redirect:a,firstmatch:l,breadcrumb:s})},[u,p,f,i,o,a,l,s,c,r]}class xt extends tt{constructor(t){super(),X(this,t,yt,wt,i,{path:3,fallback:4,redirect:5,firstmatch:6,breadcrumb:7})}}function vt(t){let e,n,r,c,i;const a=t[2].default,m=o(a,t,t[1],null);return{c(){e=$("div"),n=$("h1"),r=h(t[0]),c=g(),m&&m.c(),y(n,"class","card-title-gray"),y(e,"class","card")},m(t,o){f(t,e,o),p(e,n),p(n,r),p(e,c),m&&m.m(e,null),i=!0},p(t,[e]){(!i||1&e)&&v(r,t[0]),m&&m.p&&(!i||2&e)&&s(m,a,t,t[1],i?l(a,t[1],e,null):u(t[1]),null)},i(t){i||(P(m,t),i=!0)},o(t){Z(m,t),i=!1},d(t){t&&d(e),m&&m.d(t)}}}function _t(t,e,n){let{$$slots:r={},$$scope:c}=e,{title:i}=e;return t.$$set=t=>{"title"in t&&n(0,i=t.title),"$$scope"in t&&n(1,c=t.$$scope)},[i,c,r]}class kt extends tt{constructor(t){super(),X(this,t,_t,vt,i,{title:0})}}function St(e){let n,i,o,a,l,s,u,m,b,x;return{c(){n=$("div"),i=$("div"),o=$("div"),a=$("label"),l=h(e[1]),s=g(),u=$("div"),m=$("input"),y(a,"class","descr-gray"),y(o,"class","md:w-1/3"),y(m,"class","widget-input-indigo"),y(m,"type","text"),y(u,"class","md:w-2/3"),y(i,"class","card-items"),y(n,"class","container")},m(t,r){f(t,n,r),p(n,i),p(i,o),p(o,a),p(a,l),p(i,s),p(i,u),p(u,m),_(m,e[0]),b||(x=[w(m,"input",e[2]),w(m,"change",(function(){c(te(e[0]))&&te(e[0]).apply(this,arguments)}))],b=!0)},p(t,[n]){e=t,2&n&&v(l,e[1]),1&n&&m.value!==e[0]&&_(m,e[0])},i:t,o:t,d(t){t&&d(n),b=!1,r(x)}}}function Et(t,e,n){let{title:r}=e,{value:c}=e;return t.$$set=t=>{"title"in t&&n(1,r=t.title),"value"in t&&n(0,c=t.value)},[c,r,function(){c=this.value,n(0,c)}]}class Ot extends tt{constructor(t){super(),X(this,t,Et,St,i,{title:1,value:0})}}function Ht(t){let e,n,i;return{c(){e=$("input"),y(e,"class","widget-input-indigo text-right"),y(e,"step","0.1"),y(e,"type","number")},m(r,o){f(r,e,o),_(e,t[0]),n||(i=[w(e,"change",(function(){c(te(t[3],t[2],t[0]))&&te(t[3],t[2],t[0]).apply(this,arguments)})),w(e,"input",t[5])],n=!0)},p(n,r){t=n,1&r&&x(e.value)!==t[0]&&_(e,t[0])},d(t){t&&d(e),n=!1,r(i)}}}function Nt(t){let e,n,i;return{c(){e=$("input"),y(e,"class","widget-input-indigo text-right"),y(e,"type","text")},m(r,o){f(r,e,o),_(e,t[0]),n||(i=[w(e,"change",(function(){c(te(t[3],t[2],t[0]))&&te(t[3],t[2],t[0]).apply(this,arguments)})),w(e,"input",t[6])],n=!0)},p(n,r){t=n,1&r&&e.value!==t[0]&&_(e,t[0])},d(t){t&&d(e),n=!1,r(i)}}}function At(t){let e,n,i;return{c(){e=$("input"),y(e,"class","widget-input-indigo text-right"),y(e,"type","date")},m(r,o){f(r,e,o),_(e,t[0]),n||(i=[w(e,"change",(function(){c(te(t[3],t[2],t[0]))&&te(t[3],t[2],t[0]).apply(this,arguments)})),w(e,"input",t[7])],n=!0)},p(n,r){t=n,1&r&&_(e,t[0])},d(t){t&&d(e),n=!1,r(i)}}}function Mt(t){let e,n,i;return{c(){e=$("input"),y(e,"class","widget-input-indigo text-right"),y(e,"type","time")},m(r,o){f(r,e,o),_(e,t[0]),n||(i=[w(e,"change",(function(){c(te(t[3],t[2],t[0]))&&te(t[3],t[2],t[0]).apply(this,arguments)})),w(e,"input",t[8])],n=!0)},p(n,r){t=n,1&r&&_(e,t[0])},d(t){t&&d(e),n=!1,r(i)}}}function Ct(e){let n,r,c,i,o,a,l,s,u,m=(e[1]?e[1]:"")+"",b="number"==e[4]&&Ht(e),w="text"==e[4]&&Nt(e),x="date"==e[4]&&At(e),_="time"==e[4]&&Mt(e);return{c(){n=$("div"),r=$("div"),c=$("label"),i=h(m),o=g(),a=$("div"),b&&b.c(),l=g(),w&&w.c(),s=g(),x&&x.c(),u=g(),_&&_.c(),y(c,"class","widget-descr-gray-left"),y(r,"class","md:w-2/3 lg:w-2/3 2xl:w-2/3"),y(a,"class","md:w-1/3 lg:w-1/3 2xl:w-1/3"),y(n,"class","card-items")},m(t,e){f(t,n,e),p(n,r),p(r,c),p(c,i),p(n,o),p(n,a),b&&b.m(a,null),p(a,l),w&&w.m(a,null),p(a,s),x&&x.m(a,null),p(a,u),_&&_.m(a,null)},p(t,[e]){2&e&&m!==(m=(t[1]?t[1]:"")+"")&&v(i,m),"number"==t[4]?b?b.p(t,e):(b=Ht(t),b.c(),b.m(a,l)):b&&(b.d(1),b=null),"text"==t[4]?w?w.p(t,e):(w=Nt(t),w.c(),w.m(a,s)):w&&(w.d(1),w=null),"date"==t[4]?x?x.p(t,e):(x=At(t),x.c(),x.m(a,u)):x&&(x.d(1),x=null),"time"==t[4]?_?_.p(t,e):(_=Mt(t),_.c(),_.m(a,null)):_&&(_.d(1),_=null)},i:t,o:t,d(t){t&&d(n),b&&b.d(),w&&w.d(),x&&x.d(),_&&_.d()}}}function Rt(t,e,n){let{value:r}=e,{descr:c}=e,{topic:i}=e,{ws:o}=e,{type:a}=e;return t.$$set=t=>{"value"in t&&n(0,r=t.value),"descr"in t&&n(1,c=t.descr),"topic"in t&&n(2,i=t.topic),"ws"in t&&n(3,o=t.ws),"type"in t&&n(4,a=t.type)},[r,c,i,o,a,function(){r=x(this.value),n(0,r)},function(){r=this.value,n(0,r)},function(){r=this.value,n(0,r)},function(){r=this.value,n(0,r)}]}class zt extends tt{constructor(t){super(),X(this,t,Rt,Ct,i,{value:0,descr:1,topic:2,ws:3,type:4})}}function Tt(t,e,n){const r=t.slice();return r[7]=e[n],r[8]=e,r[9]=n,r}function qt(t,e,n){const r=t.slice();return r[7]=e[n],r[10]=e,r[9]=n,r}function It(t,e,n){const r=t.slice();return r[7]=e[n],r[11]=e,r[9]=n,r}function Ft(t){let e,n,r;function c(e){t[3](e,t[7])}let i={descr:t[7].descr,topic:t[7].topic,ws:1,type:t[7].type};return void 0!==t[7].status&&(i.value=t[7].status),e=new zt({props:i}),A.push((()=>G(e,"value",c))),{c(){Q(e.$$.fragment)},m(t,n){U(e,t,n),r=!0},p(r,c){t=r;const i={};1&c&&(i.descr=t[7].descr),1&c&&(i.topic=t[7].topic),1&c&&(i.type=t[7].type),!n&&1&c&&(n=!0,i.value=t[7].status,F((()=>n=!1))),e.$set(i)},i(t){r||(P(e.$$.fragment,t),r=!0)},o(t){Z(e.$$.fragment,t),r=!1},d(t){V(e,t)}}}function Lt(t){let e,n,r="input"===t[7].widget&&Ft(t);return{c(){r&&r.c(),e=b()},m(t,c){r&&r.m(t,c),f(t,e,c),n=!0},p(t,n){"input"===t[7].widget?r?(r.p(t,n),1&n&&P(r,1)):(r=Ft(t),r.c(),P(r,1),r.m(e.parentNode,e)):r&&(D(),Z(r,1,1,(()=>{r=null})),K())},i(t){n||(P(r),n=!0)},o(t){Z(r),n=!1},d(t){r&&r.d(t),t&&d(e)}}}function Wt(t){let e,n,r=t[0],c=[];for(let e=0;eZ(c[t],1,1,(()=>{c[t]=null}));return{c(){for(let t=0;tG(e,"value",c))),{c(){Q(e.$$.fragment)},m(t,n){U(e,t,n),r=!0},p(r,c){t=r;const i={};1&c&&(i.descr=t[7].descr),1&c&&(i.topic=t[7].topic),1&c&&(i.type=t[7].type),!n&&1&c&&(n=!0,i.value=t[7].status,F((()=>n=!1))),e.$set(i)},i(t){r||(P(e.$$.fragment,t),r=!0)},o(t){Z(e.$$.fragment,t),r=!1},d(t){V(e,t)}}}function Bt(t){let e,n,r="input"===t[7].widget&&jt(t);return{c(){r&&r.c(),e=b()},m(t,c){r&&r.m(t,c),f(t,e,c),n=!0},p(t,n){"input"===t[7].widget?r?(r.p(t,n),1&n&&P(r,1)):(r=jt(t),r.c(),P(r,1),r.m(e.parentNode,e)):r&&(D(),Z(r,1,1,(()=>{r=null})),K())},i(t){n||(P(r),n=!0)},o(t){Z(r),n=!1},d(t){r&&r.d(t),t&&d(e)}}}function Jt(t){let e,n,r=t[0],c=[];for(let e=0;eZ(c[t],1,1,(()=>{c[t]=null}));return{c(){for(let t=0;tG(e,"value",c))),{c(){Q(e.$$.fragment)},m(t,n){U(e,t,n),r=!0},p(r,c){t=r;const i={};1&c&&(i.descr=t[7].descr),1&c&&(i.topic=t[7].topic),1&c&&(i.type=t[7].type),!n&&1&c&&(n=!0,i.value=t[7].status,F((()=>n=!1))),e.$set(i)},i(t){r||(P(e.$$.fragment,t),r=!0)},o(t){Z(e.$$.fragment,t),r=!1},d(t){V(e,t)}}}function Dt(t){let e,n,r="input"===t[7].widget&&Yt(t);return{c(){r&&r.c(),e=b()},m(t,c){r&&r.m(t,c),f(t,e,c),n=!0},p(t,n){"input"===t[7].widget?r?(r.p(t,n),1&n&&P(r,1)):(r=Yt(t),r.c(),P(r,1),r.m(e.parentNode,e)):r&&(D(),Z(r,1,1,(()=>{r=null})),K())},i(t){n||(P(r),n=!0)},o(t){Z(r),n=!1},d(t){r&&r.d(t),t&&d(e)}}}function Kt(t){let e,n,r=t[0],c=[];for(let e=0;eZ(c[t],1,1,(()=>{c[t]=null}));return{c(){for(let t=0;t \n Включить светодиод статуса подключения',a=g(),l=$("div"),l.innerHTML='
',y(o,"class","card-items"),y(l,"class","md:flex md:items-center")},m(t,e){U(n,t,e),f(t,r,e),U(c,t,e),f(t,i,e),f(t,o,e),f(t,a,e),f(t,l,e),s=!0},p:t,i(t){s||(P(n.$$.fragment,t),P(c.$$.fragment,t),s=!0)},o(t){Z(n.$$.fragment,t),Z(c.$$.fragment,t),s=!1},d(t){V(n,t),t&&d(r),V(c,t),t&&d(i),t&&d(o),t&&d(a),t&&d(l)}}}function Qt(t){let e,n;return e=new kt({props:{title:"Подключение к WiFi роутеру",$$slots:{default:[Gt]},$$scope:{ctx:t}}}),{c(){Q(e.$$.fragment)},m(t,r){U(e,t,r),n=!0},p(t,n){const r={};8192&n&&(r.$$scope={dirty:n,ctx:t}),e.$set(r)},i(t){n||(P(e.$$.fragment,t),n=!0)},o(t){Z(e.$$.fragment,t),n=!1},d(t){V(e,t)}}}function Ut(t){let e,n,r,c;return{c(){e=$("textarea"),y(e,"rows","10"),y(e,"class","widget-input-indigo w-full"),y(e,"id","text1"),e.value=n=t[1](JSON.stringify(t[0]))},m(n,i){f(n,e,i),r||(c=w(e,"input",t[2]),r=!0)},p(t,r){1&r&&n!==(n=t[1](JSON.stringify(t[0])))&&(e.value=n)},d(t){t&&d(e),r=!1,c()}}}function Vt(t){let e,n,r;return n=new kt({props:{title:"Редактор JSON",$$slots:{default:[Ut]},$$scope:{ctx:t}}}),{c(){e=$("div"),Q(n.$$.fragment),y(e,"class","cards-grid")},m(t,c){f(t,e,c),U(n,e,null),r=!0},p(t,e){const r={};8193&e&&(r.$$scope={dirty:e,ctx:t}),n.$set(r)},i(t){r||(P(n.$$.fragment,t),r=!0)},o(t){Z(n.$$.fragment,t),r=!1},d(t){t&&d(e),V(n)}}}function Xt(t){let e,n,r,c,i,o,a,l,s,u,m,h,b,w,x,v,_,k,S,E,O,H,N,A,M,C,R,z,T,q,I,F,L,W,j,B,J;return q=new xt({props:{path:"/",$$slots:{default:[Pt]},$$scope:{ctx:t}}}),F=new xt({props:{path:"/config",$$slots:{default:[Zt]},$$scope:{ctx:t}}}),W=new xt({props:{path:"/connection",$$slots:{default:[Qt]},$$scope:{ctx:t}}}),B=new xt({props:{path:"/utilities",$$slots:{default:[Vt]},$$scope:{ctx:t}}}),{c(){e=$("main"),n=$("div"),n.innerHTML='',r=g(),c=$("ul"),i=$("input"),o=g(),a=$("label"),a.innerHTML="",l=g(),s=$("ul"),u=$("li"),m=$("a"),m.textContent="Управление",h=g(),b=$("li"),w=$("a"),w.textContent="Конфигуратор",x=g(),v=$("li"),_=$("a"),_.textContent="Подключение",k=g(),S=$("li"),E=$("a"),E.textContent="Утилиты",O=g(),H=$("li"),N=$("a"),N.textContent="Лог",A=g(),M=$("li"),C=$("a"),C.textContent="О проекте",R=g(),z=$("ul"),T=$("div"),Q(q.$$.fragment),I=g(),Q(F.$$.fragment),L=g(),Q(W.$$.fragment),j=g(),Q(B.$$.fragment),y(n,"class","fixed m-0 h-10 w-full bg-gray-100 shadow-md"),y(i,"id","menu__toggle"),y(i,"type","checkbox"),y(a,"class","menu__btn"),y(a,"for","menu__toggle"),y(m,"class","menu__item"),y(m,"href","/"),y(w,"class","menu__item"),y(w,"href","/config"),y(_,"class","menu__item"),y(_,"href","/connection"),y(E,"class","menu__item"),y(E,"href","/utilities"),y(N,"class","menu__item"),y(N,"href","/log"),y(C,"class","menu__item"),y(C,"href","/about"),y(s,"class","menu__box"),y(c,"class","menu__ham"),y(T,"class","bg-cover bg-gray-50 pt-16"),y(z,"class","menu__main")},m(t,d){f(t,e,d),p(e,n),p(e,r),p(e,c),p(c,i),p(c,o),p(c,a),p(c,l),p(c,s),p(s,u),p(u,m),p(s,h),p(s,b),p(b,w),p(s,x),p(s,v),p(v,_),p(s,k),p(s,S),p(S,E),p(s,O),p(s,H),p(H,N),p(s,A),p(s,M),p(M,C),p(e,R),p(e,z),p(z,T),U(q,T,null),p(T,I),U(F,T,null),p(T,L),U(W,T,null),p(T,j),U(B,T,null),J=!0},p(t,[e]){const n={};8193&e&&(n.$$scope={dirty:e,ctx:t}),q.$set(n);const r={};8192&e&&(r.$$scope={dirty:e,ctx:t}),F.$set(r);const c={};8192&e&&(c.$$scope={dirty:e,ctx:t}),W.$set(c);const i={};8193&e&&(i.$$scope={dirty:e,ctx:t}),B.$set(i)},i(t){J||(P(q.$$.fragment,t),P(F.$$.fragment,t),P(W.$$.fragment,t),P(B.$$.fragment,t),J=!0)},o(t){Z(q.$$.fragment,t),Z(F.$$.fragment,t),Z(W.$$.fragment,t),Z(B.$$.fragment,t),J=!1},d(t){t&&d(e),V(q),V(F),V(W),V(B)}}}function te(t,e,n){console.log(t,e,n)}function ee(t,e,n){mt.mode.hash(),O((async()=>{console.log("mounted")}));let r=[];return r=[{widget:"input",type:"time",status:"12:00",page:"Inputs",order:"3",descr:"Switch on boiler time",topic:"/prefix/00000-00003/temp3"},{widget:"input",type:"number",status:"30.5",after:"°С",page:"Inputs",order:"1",descr:"Boiler temperature",topic:"/prefix/00000-00001/temp1"},{widget:"input",type:"text",status:"Hello",page:"Inputs",order:"3",descr:"Massage to be send",topic:"/prefix/00000-00003/temp3"},{widget:"input",type:"date",status:"2021.09.15",page:"Inputs",order:"2",descr:"Switch on boiler date",topic:"/prefix/00000-00002/temp2"}],[r,t=>{try{t=JSON.stringify(JSON.parse(t),null,4)}catch(e){return t}return t=(t=t.replace(/&/g,"&").replace(//g,">")).replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g,(function(t){return t}))},function(){n(0,r=JSON.parse(document.getElementById("text1").value))},function(e,c){t.$$.not_equal(c.status,e)&&(c.status=e,n(0,r))},function(e,c){t.$$.not_equal(c.status,e)&&(c.status=e,n(0,r))},function(e,c){t.$$.not_equal(c.status,e)&&(c.status=e,n(0,r))}]}return new class extends tt{constructor(t){super(),X(this,t,ee,Xt,i,{})}}({target:document.body,props:{name:"world"}})}(); +//# sourceMappingURL=bundle.js.map diff --git a/data_svelte/check.json b/data_svelte/check.json new file mode 100644 index 00000000..e69de29b diff --git a/data_svelte/config.json b/data_svelte/config.json new file mode 100644 index 00000000..d8a730c6 --- /dev/null +++ b/data_svelte/config.json @@ -0,0 +1,37 @@ +{ + "name": "IoTmanager", + "chipID": "", + "apssid": "IoTmanager", + "appass": "", + "routerssid": "dlink", + "routerpass": "", + "timezone": 1, + "ntp": "pool.ntp.org", + "mqttServer": "91.204.228.124", + "mqttPort": 1883, + "mqttPrefix": "/iotTest3", + "mqttUser": "rise", + "mqttPass": "23ri22se32", + "mqttServer2": "M2.WQTT.RU", + "mqttPort2": 8021, + "mqttPrefix2": "/iotTest3", + "mqttUser2": "rise", + "mqttPass2": "hostel3333", + "scen": "1", + "telegramApi": "1416711569:AAEI0j83GmXqwzb_gnK1B0Am0gDwZoJt5xo", + "telegonof": "0", + "teleginput": "0", + "autos": "1", + "weblogin": "admin", + "webpass": "admin", + "MqttIn": "0", + "MqttOut": "0", + "blink": "0", + "oneWirePin": "2", + "serverip": "http://206.189.49.244", + "uart": "0", + "uartS": "9600", + "uartTX": "12", + "uartRX": "13", + "grafmax": "0" +} \ No newline at end of file diff --git a/data_svelte/favicon.png b/data_svelte/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..7e6f5eb5a2f1f1c882d265cf479de25caa925645 GIT binary patch literal 3127 zcmV-749N3|P)i z7)}s4L53SJCkR}iVi00SFk;`MXX*#X*kkwKs@nFGS}c;=?XFjU|G$3t^5sjIVS2G+ zw)WGF83CpoGXhLGW(1gW%uV|X7>1P6VhCX=Ux)Lb!*DZ%@I3!{Gsf7d?gtIQ%nQiK z3%(LUSkBji;C5Rfgd6$VsF@H`Pk@xtY6t<>FNR-pD}=C~$?)9pdm3XZ36N5PNWYjb z$xd$yNQR9N!dfj-Vd@BwQo^FIIWPPmT&sZyQ$v81(sCBV=PGy{0wltEjB%~h157*t zvbe_!{=I_783x!0t1-r#-d{Y?ae$Q4N_Nd^Ui^@y(%)Gjou6y<3^XJdu{rmUf-Me?)zZ>9OR&6U5H*cK; z$gUlB{g0O4gN0sLSO|Of?hU(l?;h(jA3uH!Z{EBKuV23ouU@^Y6#%v+QG;>e*E}%?wlu-NT4DG zs)z)7WbLr)vGAu(ohrKc^em@OpO&f~6_>E61n_e0_V3@{U3^O;j{`^mNCJUj_>;7v zsMs6Hu3g7+@v+lSo;=yTYFqq}jZmQ-BK8K{C4kqi_i*jBaQE(Au0607V-zKeT;EPg zX(`vrn=L+e74+-Tqeok@_`tDa$G9I|$nTU5H*2V8@y()n*zqM?J1G!-1aX;CfDC9B zTnJ#j_%*n8Qb1)re*Bno7g0RG{Eb;IK14irJYJp$5Z6ac9~b_P?+5t~95~SRG$g?1 znFJ7p$xV&GZ18m~79TGRdfsc-BcX$9yXTR*n)mPD@1~O(_?cT$ZvFPucRmGlq&se0 zKrcUf^k}4hM*biEJOWKzz!qQe;CB_ZtSOO9Owg#lZAc=s65^rb{fZe(TYu_rk!wKkEf}RIt=#Om( zR8mN`DM<^xj~59euMMspBolVN zAPTr8sSDI104orIAdmL$uOXn*6hga1G+0WD0E?UtabxC#VC~vf3|10|phW;yQ3CY8 z2CM=)ErF;xq-YJ5G|um}>*1#E+O_Mu|Nr#qQ&G1P-NMq@f?@*XUcSbV?tX=)ilM-Q zBZP|!Bpv0V;#ojKcpc7$=eqO;#Uy~#?^kNI{vSZfLx&DEt~LTmaKWXcx=joubklI<*Aw z>LtMaQ7DR<1I2LkWvwyu#Rwn~;ezT}_g(@5l3h?W%-a86Y-t#O1PubP+z<%?V5D(U zy57A6{h+{?kOZp7&WKZR+=sznMJ}+Dnpo=C_0%R_x_t~J5T?E_{+))l5v1%52>)d-`iiZyx|5!%M2Fb2dU zW3~MwwpEH9Rhue+k$UIOoo($Ds!NbOyMR36fRHu;*15(YcA7siIZk#%JWz>P!qX1?IUojG&nKR>^gArBt2 zit(ETyZ=@V&7mv_Fi4bABcnwP+jzQuHcfU&BrAV91u-rFvEi7y-KnWsvHH=d2 zgAk(GKm_S8RcTJ>2N3~&Hbwp{Z3NF_Xeh}g4Eke)V&dY{W(3&b1j9t4yK_aYJisZZ{1rcU5- z;eD>K;ndPq&B-8yA_S0F!4ThA&{1{x)H<#?k9a#6Pc6L?V^s0``ynL&D;p(!Nmx`Y zFkHex{4p!Ggm^@DlehW}iHHVi}~u=$&N? z(NEBLQ#UxxAkdW>X9LnqUr#t4Lu0=9L8&o>JsqTtT5|%gb3QA~hr0pED71+iFFr)dZ=Q=E6ng{NE{Z~0)C?deO#?Aj zSDQ$z#TeC2T^|=}6GBo-&$;E{HL3!q3Z-szuf)O=G#zDjin4SSP%o%6+2IT#sLjQa ziyxFFz~LMjWY+_a5H!U6%a<=b7QVP^ z*90a62;bVq{?@)P6^DWd^Yilq4|YTV2Nw!Yu;a1lPI-sxR)rf@Fe5DhDP7FH zZZ%4S*1C30P;|O+jB!1;m|rXT90Sm5*RBbQN`PKu+hDD*S^yE(CdtSfg=z>u$cIj> z + + + + + + Svelte app + + + + + + + + + + diff --git a/data_svelte/setup.json b/data_svelte/setup.json new file mode 100644 index 00000000..88a9ae69 --- /dev/null +++ b/data_svelte/setup.json @@ -0,0 +1,542 @@ +[ + { + "type": "button-out", + "web": { + "widget": "toggle", + "page": "Кнопки", + "descr": "Освещение", + "order": 1, + "topic": "/prefix/123456-123456/btn1" + }, + "set": { + "id": "btn1", + "gpio": 1, + "inv": false + } + }, + { + "type": "button-out", + "web": { + "widget": "toggle", + "page": "Кнопки", + "descr": "Освещение", + "order": 1, + "topic": "/prefix/123456-123456/btn1" + }, + "set": { + "id": "btn1", + "gpio": 2, + "inv": false + } + }, + { + "type": "button-out", + "web": { + "widget": "toggle", + "page": "Кнопки", + "descr": "Освещение", + "order": 1, + "topic": "/prefix/123456-123456/btn1" + }, + "set": { + "id": "btn1", + "gpio": 3, + "inv": false + } + }, + { + "type": "button-out", + "web": { + "widget": "toggle", + "page": "Кнопки", + "descr": "Освещение", + "order": 1, + "topic": "/prefix/123456-123456/btn1" + }, + "set": { + "id": "btn1", + "gpio": 4, + "inv": false + } + }, + { + "type": "button-out", + "web": { + "widget": "toggle", + "page": "Кнопки", + "descr": "Освещение", + "order": 1, + "topic": "/prefix/123456-123456/btn1" + }, + "set": { + "id": "btn1", + "gpio": 5, + "inv": false + } + }, + { + "type": "button-out", + "web": { + "widget": "toggle", + "page": "Кнопки", + "descr": "Освещение", + "order": 1, + "topic": "/prefix/123456-123456/btn1" + }, + "set": { + "id": "btn1", + "gpio": 6, + "inv": false + } + }, + { + "type": "button-out", + "web": { + "widget": "toggle", + "page": "Кнопки", + "descr": "Освещение", + "order": 1, + "topic": "/prefix/123456-123456/btn1" + }, + "set": { + "id": "btn1", + "gpio": 7, + "inv": false + } + }, + { + "type": "button-out", + "web": { + "widget": "toggle", + "page": "Кнопки", + "descr": "Освещение", + "order": 1, + "topic": "/prefix/123456-123456/btn1" + }, + "set": { + "id": "btn1", + "gpio": 8, + "inv": false + } + }, + { + "type": "button-out", + "web": { + "widget": "toggle", + "page": "Кнопки", + "descr": "Освещение", + "order": 1, + "topic": "/prefix/123456-123456/btn1" + }, + "set": { + "id": "btn1", + "gpio": 9, + "inv": false + } + }, + { + "type": "button-out", + "web": { + "widget": "toggle", + "page": "Кнопки", + "descr": "Освещение", + "order": 1, + "topic": "/prefix/123456-123456/btn1" + }, + "set": { + "id": "btn1", + "gpio": 10, + "inv": false + } + }, + { + "type": "button-out", + "web": { + "widget": "toggle", + "page": "Кнопки", + "descr": "Освещение", + "order": 1, + "topic": "/prefix/123456-123456/btn1" + }, + "set": { + "id": "btn1", + "gpio": 11, + "inv": false + } + }, + { + "type": "button-out", + "web": { + "widget": "toggle", + "page": "Кнопки", + "descr": "Освещение", + "order": 1, + "topic": "/prefix/123456-123456/btn1" + }, + "set": { + "id": "btn1", + "gpio": 12, + "inv": false + } + }, + { + "type": "button-out", + "web": { + "widget": "toggle", + "page": "Кнопки", + "descr": "Освещение", + "order": 1, + "topic": "/prefix/123456-123456/btn1" + }, + "set": { + "id": "btn1", + "gpio": 13, + "inv": false + } + }, + { + "type": "button-out", + "web": { + "widget": "toggle", + "page": "Кнопки", + "descr": "Освещение", + "order": 1, + "topic": "/prefix/123456-123456/btn1" + }, + "set": { + "id": "btn1", + "gpio": 14, + "inv": false + } + }, + { + "type": "button-out", + "web": { + "widget": "toggle", + "page": "Кнопки", + "descr": "Освещение", + "order": 1, + "topic": "/prefix/123456-123456/btn1" + }, + "set": { + "id": "btn1", + "gpio": 15, + "inv": false + } + }, + { + "type": "button-out", + "web": { + "widget": "toggle", + "page": "Кнопки", + "descr": "Освещение", + "order": 1, + "topic": "/prefix/123456-123456/btn1" + }, + "set": { + "id": "btn1", + "gpio": 16, + "inv": false + } + }, + { + "type": "button-out", + "web": { + "widget": "toggle", + "page": "Кнопки", + "descr": "Освещение", + "order": 1, + "topic": "/prefix/123456-123456/btn1" + }, + "set": { + "id": "btn1", + "gpio": 17, + "inv": false + } + }, + { + "type": "button-out", + "web": { + "widget": "toggle", + "page": "Кнопки", + "descr": "Освещение", + "order": 1, + "topic": "/prefix/123456-123456/btn1" + }, + "set": { + "id": "btn1", + "gpio": 18, + "inv": false + } + }, + { + "type": "button-out", + "web": { + "widget": "toggle", + "page": "Кнопки", + "descr": "Освещение", + "order": 1, + "topic": "/prefix/123456-123456/btn1" + }, + "set": { + "id": "btn1", + "gpio": 19, + "inv": false + } + }, + { + "type": "button-out", + "web": { + "widget": "toggle", + "page": "Кнопки", + "descr": "Освещение", + "order": 1, + "topic": "/prefix/123456-123456/btn1" + }, + "set": { + "id": "btn1", + "gpio": 20, + "inv": false + } + }, + { + "type": "button-out", + "web": { + "widget": "toggle", + "page": "Кнопки", + "descr": "Освещение", + "order": 1, + "topic": "/prefix/123456-123456/btn1" + }, + "set": { + "id": "btn1", + "gpio": 21, + "inv": false + } + }, + { + "type": "button-out", + "web": { + "widget": "toggle", + "page": "Кнопки", + "descr": "Освещение", + "order": 1, + "topic": "/prefix/123456-123456/btn1" + }, + "set": { + "id": "btn1", + "gpio": 22, + "inv": false + } + }, + { + "type": "button-out", + "web": { + "widget": "toggle", + "page": "Кнопки", + "descr": "Освещение", + "order": 1, + "topic": "/prefix/123456-123456/btn1" + }, + "set": { + "id": "btn1", + "gpio": 23, + "inv": false + } + }, + { + "type": "button-out", + "web": { + "widget": "toggle", + "page": "Кнопки", + "descr": "Освещение", + "order": 1, + "topic": "/prefix/123456-123456/btn1" + }, + "set": { + "id": "btn1", + "gpio": 24, + "inv": false + } + }, + { + "type": "button-out", + "web": { + "widget": "toggle", + "page": "Кнопки", + "descr": "Освещение", + "order": 1, + "topic": "/prefix/123456-123456/btn1" + }, + "set": { + "id": "btn1", + "gpio": 25, + "inv": false + } + }, + { + "type": "button-out", + "web": { + "widget": "toggle", + "page": "Кнопки", + "descr": "Освещение", + "order": 1, + "topic": "/prefix/123456-123456/btn1" + }, + "set": { + "id": "btn1", + "gpio": 26, + "inv": false + } + }, + { + "type": "button-out", + "web": { + "widget": "toggle", + "page": "Кнопки", + "descr": "Освещение", + "order": 1, + "topic": "/prefix/123456-123456/btn1" + }, + "set": { + "id": "btn1", + "gpio": 27, + "inv": false + } + }, + { + "type": "button-out", + "web": { + "widget": "toggle", + "page": "Кнопки", + "descr": "Освещение", + "order": 1, + "topic": "/prefix/123456-123456/btn1" + }, + "set": { + "id": "btn1", + "gpio": 28, + "inv": false + } + }, + { + "type": "button-out", + "web": { + "widget": "toggle", + "page": "Кнопки", + "descr": "Освещение", + "order": 1, + "topic": "/prefix/123456-123456/btn1" + }, + "set": { + "id": "btn1", + "gpio": 29, + "inv": false + } + }, + { + "type": "button-out", + "web": { + "widget": "toggle", + "page": "Кнопки", + "descr": "Освещение", + "order": 1, + "topic": "/prefix/123456-123456/btn1" + }, + "set": { + "id": "btn1", + "gpio": 30, + "inv": false + } + }, + { + "type": "button-out", + "web": { + "widget": "toggle", + "page": "Кнопки", + "descr": "Освещение", + "order": 1, + "topic": "/prefix/123456-123456/btn1" + }, + "set": { + "id": "btn1", + "gpio": 31, + "inv": false + } + }, + { + "type": "button-out", + "web": { + "widget": "toggle", + "page": "Кнопки", + "descr": "Освещение", + "order": 1, + "topic": "/prefix/123456-123456/btn1" + }, + "set": { + "id": "btn1", + "gpio": 32, + "inv": false + } + }, + { + "type": "button-out", + "web": { + "widget": "toggle", + "page": "Кнопки", + "descr": "Освещение", + "order": 1, + "topic": "/prefix/123456-123456/btn1" + }, + "set": { + "id": "btn1", + "gpio": 33, + "inv": false + } + }, + { + "type": "button-out", + "web": { + "widget": "toggle", + "page": "Кнопки", + "descr": "Освещение", + "order": 1, + "topic": "/prefix/123456-123456/btn1" + }, + "set": { + "id": "btn1", + "gpio": 34, + "inv": false + } + }, + { + "type": "button-out", + "web": { + "widget": "toggle", + "page": "Кнопки", + "descr": "Освещение", + "order": 1, + "topic": "/prefix/123456-123456/btn1" + }, + "set": { + "id": "btn1", + "gpio": 35, + "inv": false + } + }, + { + "type": "button-out", + "web": { + "widget": "toggle", + "page": "Кнопки", + "descr": "Освещение", + "order": 2, + "topic": "/prefix/123456-123456/btn2" + }, + "set": { + "id": "btn2", + "gpio": 36, + "inv": false + } + } +] \ No newline at end of file diff --git a/data_svelte/sync.ffs_db b/data_svelte/sync.ffs_db new file mode 100644 index 0000000000000000000000000000000000000000..cd5cdf88e9c6cf1c9b78c0920fe43edb193cb8fd GIT binary patch literal 188 zcmZ=ON=zrNUvL=f|K$b~NfuRd1$PC1FKnwzt zIkmn_lvg}Hq5BA%5#x%J%q|%~Q3a5GAgGAbwtGMK00WDS!=9gyYuf+0_Be=fI~&fb zFpros^x8fTIobUu;EuAja0;I= f^R6FDj@~#b-f;GKh`s14Ifg?lPS*V9|1JXnwpT$2 literal 0 HcmV?d00001 diff --git a/include/main.h b/include/main.h new file mode 100644 index 00000000..d1f7fc89 --- /dev/null +++ b/include/main.h @@ -0,0 +1,14 @@ +#include +#include +#include +#include + +#include "LittleFS.h" + +extern FS LittleFS; +using littlefs_impl::LittleFSConfig; +extern FS* filesystem; +#define FileFS LittleFS +#define FS_NAME "LittleFS" + +void setupESP(); diff --git a/include/rest.h b/include/rest.h new file mode 100644 index 00000000..09e6942a --- /dev/null +++ b/include/rest.h @@ -0,0 +1,9 @@ +#include "main.h" + +File seekFile(const String& filename, size_t position = 0); +const String writeFile(const String& filename, const String& str); +const String readFile(const String& filename, size_t max_size); +const String filepath(const String& filename); +bool fileSystemInit(); +String prettyBytes(size_t size); +bool cutFile(const String& src, const String& dst); \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/.clang-format b/lib/ArduinoStreamUtils/.clang-format new file mode 100644 index 00000000..76317270 --- /dev/null +++ b/lib/ArduinoStreamUtils/.clang-format @@ -0,0 +1,5 @@ +BasedOnStyle: Google +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +IncludeBlocks: Preserve \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/.github/FUNDING.yml b/lib/ArduinoStreamUtils/.github/FUNDING.yml new file mode 100644 index 00000000..0ec57d0a --- /dev/null +++ b/lib/ArduinoStreamUtils/.github/FUNDING.yml @@ -0,0 +1,4 @@ +github: bblanchon +custom: + - https://arduinojson.org/book/ + - https://donate.benoitblanchon.fr/ diff --git a/lib/ArduinoStreamUtils/.github/workflows/ci.yml b/lib/ArduinoStreamUtils/.github/workflows/ci.yml new file mode 100644 index 00000000..4458bfe0 --- /dev/null +++ b/lib/ArduinoStreamUtils/.github/workflows/ci.yml @@ -0,0 +1,121 @@ +name: Continuous Integration + +on: [push, pull_request] + +jobs: + test: + name: Unit Test + runs-on: ubuntu-latest + steps: + - name: Install + run: sudo apt-get install -y lcov + - name: Checkout + uses: actions/checkout@v2 + - name: Configure + run: cmake -DCOVERAGE=true . + - name: Build + run: cmake --build . + - name: Run tests + run: ctest --output-on-failure . + - name: Capture coverage data + run: lcov --capture --no-external --directory . --output-file coverage.info + - name: Filter coverage data + run: lcov --remove coverage.info "$(pwd)/extras/*" --output-file coverage_filtered.info + - name: Generate coverage report + run: mkdir coverage && genhtml coverage_filtered.info -o coverage -t ArduinoJson + - name: Upload coverage report + uses: actions/upload-artifact@v2 + with: + name: coverage + path: coverage + - name: Upload to Coveralls + uses: coverallsapp/github-action@master + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + path-to-lcov: coverage_filtered.info + + platformio: + name: PlatformIO + needs: test + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - platform: atmelavr + board: leonardo + eeprom: true + softwareserial: true + - platform: espressif8266 + board: huzzah + eeprom: true + softwareserial: true + - platform: espressif32 + board: esp32dev + eeprom: true + softwareserial: false + - platform: atmelsam + board: mkr1000USB + eeprom: false + softwareserial: false + - platform: teensy + board: teensy31 + eeprom: true + softwareserial: true + - platform: ststm32 + board: nucleo_f031k6 + eeprom: true + softwareserial: true + - platform: nordicnrf52 + board: adafruit_feather_nrf52840 + eeprom: false + softwareserial: true + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up cache + uses: actions/cache@v2 + with: + path: | + ~/.platformio + ~/.cache/pip + key: ${{ runner.os }}-platformio + - name: Set up Python 3.x + uses: actions/setup-python@v2 + with: + python-version: '3.x' + - name: Install PlatformIO + run: pip install platformio + - name: Install platform "${{ matrix.platform }}" + run: platformio platform install ${{ matrix.platform }} + - name: Install adafruit-nrfutil + if: ${{ matrix.platform == 'nordicnrf52' }} + run: pip3 install adafruit-nrfutil + - name: Include Adafruit_TinyUSB.h # https://github.com/adafruit/Adafruit_nRF52_Arduino/issues/653 + if: ${{ matrix.platform == 'nordicnrf52' }} + run: find examples/ -name '*.ino' -exec sed -i 's/\(#include \)/\1\n#include /' {} + + - name: Build EepromRead + if: ${{ matrix.eeprom }} + run: platformio ci "examples/EepromRead/EepromRead.ino" -l '.' -b ${{ matrix.board }} + - name: Build EepromWrite + if: ${{ matrix.eeprom }} + run: platformio ci "examples/EepromWrite/EepromWrite.ino" -l '.' -b ${{ matrix.board }} + - name: Build HammingSerial1 + run: platformio ci "examples/HammingSerial1/HammingSerial1.ino" -l '.' -b ${{ matrix.board }} + - name: Build HammingSoftwareSerial + if: ${{ matrix.softwareserial }} + run: platformio ci "examples/HammingSoftwareSerial/HammingSoftwareSerial.ino" -l '.' -b ${{ matrix.board }} + - name: Build Logger + run: platformio ci "examples/Logger/Logger.ino" -l '.' -b ${{ matrix.board }} + - name: Build ReadBuffer + run: platformio ci "examples/ReadBuffer/ReadBuffer.ino" -l '.' -b ${{ matrix.board }} + - name: Build ReadLogger + run: platformio ci "examples/ReadLogger/ReadLogger.ino" -l '.' -b ${{ matrix.board }} + - name: Build StringPrint + run: platformio ci "examples/StringPrint/StringPrint.ino" -l '.' -b ${{ matrix.board }} + - name: Build StringStream + run: platformio ci "examples/StringStream/StringStream.ino" -l '.' -b ${{ matrix.board }} + - name: Build WriteBuffer + run: platformio ci "examples/WriteBuffer/WriteBuffer.ino" -l '.' -b ${{ matrix.board }} + - name: Build WriteLogger + run: platformio ci "examples/WriteLogger/WriteLogger.ino" -l '.' -b ${{ matrix.board }} diff --git a/lib/ArduinoStreamUtils/.gitignore b/lib/ArduinoStreamUtils/.gitignore new file mode 100644 index 00000000..a7747886 --- /dev/null +++ b/lib/ArduinoStreamUtils/.gitignore @@ -0,0 +1,2 @@ +/build + diff --git a/lib/ArduinoStreamUtils/CHANGELOG.md b/lib/ArduinoStreamUtils/CHANGELOG.md new file mode 100644 index 00000000..ae38a39c --- /dev/null +++ b/lib/ArduinoStreamUtils/CHANGELOG.md @@ -0,0 +1,79 @@ +StreamUtils - Change log +======================== + +HEAD +---- + +* Support `Print::flush()` on AVR + +1.6.1 (2021/94/05) +----- + +* Add example `HammingSerial1.ino` +* Add support for STM32 (issue #11) + + +1.6.0 (2020/11/20) +----- + +* Add `HammingPrint<7, 4>` +* Add `HammingStream<7, 4>`, `HammingEncodingStream<7, 4>`, and `HammingDecodingStream<7, 4>` +* Add `HammingClient<7, 4>`, `HammingEncodingClient<7, 4>`, and `HammingDecodingClient<7, 4>` + +1.5.0 (2020/08/04) +----- + +* Add `WaitingPrint`, `WriteWaitingClient`, and `WriteWaitingStream`. + +1.4.1 (2020/07/01) +----- + +* Fix unwanted waits in `ReadBufferingClient` and `ReadBufferingStream`. +* Stop calling `Client::read()` in place of `Stream::readBytes()`, + because it doesn't honor the timeout. + +1.4.0 (2020/03/30) +----- + +* Add `EepromStream` +* Add support for ESP32 +* Add support for Teensy + +1.3.0 (2020/01/20) +----- + +* Move auxiliary content to `extras/` to comply with new library layout +* Add `StringPrint` and `StringStream` +* Extract `StreamUtils.hpp`, same as `StreamUtils.h` except it keeps everything in the `StreamUtils` namespace. + +1.2.2 (2019/07/18) +----- + +* Fix `BufferingPrint` taking `Stream` instead of `Print` (issue #3) +* Fix `LoggingPrint` not forwarding call to `Print::flush()` +* Fix missing `override` specifiers + +1.2.1 (2019/06/05) +----- + +* Remove workaround for ESP8266 core 2.5.0 +* Fix compatibility with ESP8266 core 2.5.1+ (issue #2) + +1.2.0 (2019/05/01) +----- + +* Add `LoggingPrint` +* Add `BufferingPrint` +* Add `WriteLoggingClient`, `ReadLoggingClient`, and `LoggingClient` +* Add `WriteBufferingClient` and `ReadBufferingClient` + +1.1.0 (2019/04/20) +----- + +* Add `LoggingStream` (=`ReadLoggingStream` + `WriteLoggingStream`) + +1.0.0 (2019/04/14) +----- + +* Add `ReadBufferingStream` and `WriteBufferingStream` +* Add `ReadLoggingStream` and `WriteLoggingStream` diff --git a/lib/ArduinoStreamUtils/CMakeLists.txt b/lib/ArduinoStreamUtils/CMakeLists.txt new file mode 100644 index 00000000..26061d64 --- /dev/null +++ b/lib/ArduinoStreamUtils/CMakeLists.txt @@ -0,0 +1,18 @@ +# StreamUtils - github.com/bblanchon/ArduinoStreamUtils +# Copyright Benoit Blanchon 2019-2021 +# MIT License + +cmake_minimum_required(VERSION 3.0) +project(StreamUtils) + +enable_testing() + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +if(${COVERAGE}) + set(CMAKE_CXX_FLAGS "-fprofile-arcs -ftest-coverage -g -O0") +endif() + +include_directories(${CMAKE_CURRENT_LIST_DIR}/src) +add_subdirectory(extras/test) diff --git a/lib/ArduinoStreamUtils/LICENSE.md b/lib/ArduinoStreamUtils/LICENSE.md new file mode 100644 index 00000000..4774d78c --- /dev/null +++ b/lib/ArduinoStreamUtils/LICENSE.md @@ -0,0 +1,10 @@ +The MIT License (MIT) +--------------------- + +Copyright © 2019 Benoit BLANCHON + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/lib/ArduinoStreamUtils/README.md b/lib/ArduinoStreamUtils/README.md new file mode 100644 index 00000000..a03ffe9e --- /dev/null +++ b/lib/ArduinoStreamUtils/README.md @@ -0,0 +1,369 @@ +StreamUtils: Power-ups for Arduino Streams +========================================== + +[![arduino-library-badge](https://www.ardu-badge.com/badge/StreamUtils.svg?version=1.6.1)](https://www.ardu-badge.com/StreamUtils/1.6.1) +[![Continuous Integration](https://github.com/bblanchon/ArduinoStreamUtils/workflows/Continuous%20Integration/badge.svg)](https://github.com/bblanchon/ArduinoStreamUtils/actions/workflows/ci.yml) +[![Coverage Status](https://coveralls.io/repos/github/bblanchon/ArduinoStreamUtils/badge.svg)](https://coveralls.io/github/bblanchon/ArduinoStreamUtils) + +The *stream* is an essential abstraction in Arduino; we find it in many places: + +* [`HardwareSerial`](https://www.arduino.cc/reference/en/language/functions/communication/serial/) +* [`SoftwareSerial`](https://www.arduino.cc/en/Reference/SoftwareSerial) +* [`File`](https://www.arduino.cc/en/Reference/SD) +* [`EthernetClient`](https://www.arduino.cc/en/Reference/EthernetClient) +* [`WiFiClient`](https://www.arduino.cc/en/Reference/WiFiClient) +* [`Wire`](https://www.arduino.cc/en/reference/wire) +* and many others... + +This library provides some helper classes and functions for dealing with streams. + +For example, with this library, you can: + +* speed of your program by buffering the data it reads from a file +* reduce the number of packets sent over WiFi by buffering the data you send +* improve the reliability of a serial connection by adding error correction codes +* debug your program more easily by logging what it sends to a Web service +* send large data with the [Wire library](https://www.arduino.cc/en/reference/wire) +* use a `String` or EEPROM with a stream interface + +Read on to see how StreamUtils can help you! + + +How to add buffering to a Stream? +--------------------------------- + +### Buffering read operations + +Sometimes, you can significantly improve performance by reading many bytes at once. +For example, [according to SPIFFS's wiki](https://github.com/pellepl/spiffs/wiki/Performance-and-Optimizing#reading-files), it's much faster to read files in chunks of 64 bytes than reading them one byte at a time. + +![ReadBufferingStream](extras/images/ReadBuffer.svg) + +To buffer the input, simply decorate the original `Stream` with `ReadBufferingStream`. For example, suppose your program reads a JSON document from SPIFFS, like that: + +```c++ +File file = SPIFFS.open("example.json", "r"); +deserializeJson(doc, file); +``` + +Then you simply need to insert one line to greatly improve the reading speed: + +```c++ +File file = SPIFFS.open("example.json", "r"); +ReadBufferingStream bufferedFile{file, 64}; // <- HERE +deserializeJson(doc, bufferedFile); +``` + +Unfortunately, this optimization is only possible if: + +1. `Stream.readBytes()` is declared `virtual` in your Arduino Code (as it's the case for ESP8266), and +2. the derived class has an optimized implementation of `readBytes()` (as it's the case for SPIFFS' `File`). + +When possible, prefer `ReadBufferingClient` to `ReadBufferingStream` because `Client` defines a `read()` method similar to `readBytes()`, except that this one is `virtual` on all platforms. + +If memory allocation fails, `ReadBufferingStream` behaves as if no buffer was used: it forwards all calls to the upstream `Stream`. + +Adding a buffer only makes sense for **unbuffered** streams. For example, there is **no benefit to adding a buffer to serial ports** because they already include an internal buffer. + +### Buffering write operations + +Similarly, you can improve performance significantly by writing many bytes at once. +For example, if you write to `WiFiClient` one bytes at a time, it will be very slow; it's much faster if you send large chunks. + +![WriteBufferingStream](extras/images/WriteBuffer.svg) + +To add a buffer, decorate the original `Stream` with `WriteBufferingStream`. For example, if your program sends a JSON document via `WiFiClient`, like that: + +```c++ +serializeJson(doc, wifiClient); +``` + +Then, you just need to add two lines: + +```c++ +WriteBufferingStream bufferedWifiClient{wifiClient, 64}; +serializeJson(doc, bufferedWifiClient); +bufferedWifiClient.flush(); +``` + +`flush()` sends the remaining data; if you forget to call it, the end of the message will be missing. The destructor of `WriteBufferingStream` calls `flush()`, so you can remove this line if you destroy the decorator immediately. + +If memory allocation fails, `WriteBufferingStream` behaves as if no buffer was used: it forwards all calls to the upstream `Stream`. + +Adding a buffer only makes sense for **unbuffered** streams. For example, there is **no benefit to adding a buffer to serial ports** because they already include an internal buffer. + +How to add logging to a stream? +------------------------------- + +### Logging write operations + +When debugging a program that makes HTTP requests, the first thing you want to check is that the request is correct. With this library, you can decorate the `EthernetClient` or the `WiFiClient` to log everything to the serial. + +![WriteLoggingStream](extras/images/WriteLogger.svg) + +For example, if you program is: + +```c++ +client.println("GET / HTTP/1.1"); +client.println("User-Agent: Arduino"); +// ... +``` + +Then, you just need to create the decorator, and update the calls to `println()`: + +```c++ +WriteLoggingStream loggingClient(client, Serial); +loggingClient.println("GET / HTTP/1.1"); +loggingClient.println("User-Agent: Arduino"); +// ... +``` + +Everything you write to `loggingClient` is written to `client` and logged to `Serial`. + + +### Logging read operations + +Similarly, you often want to see what the HTTP server sent back. With this library, you can decorate the `EthernetClient` or the `WiFiClient` to log everything to the serial. + +![ReadLoggingStream](extras/images/ReadLogger.svg) + +For example, if you program is: + +```c++ +char response[256]; +client.readBytes(response, 256); +``` + +Then, you just need to create the decorator, and update the calls to `readBytes()`: + +```c++ +ReadLoggingStream loggingClient(client, Serial); +char response[256]; +loggingClient.readBytes(response, 256); +// ... +``` + +`loggingClient` forwards all operations to `client` and logs read operation to `Serial`. + +⚠ **WARNING** ⚠ +If your program receives data from one serial port and logs to another one, **make sure the latter runs at a much higher speed**. Logging must be at least ten times faster, or it will slow down the receiving port, which may drop incoming bytes. + +### Logging read and write operations + +Of course, you could log read and write operations by combining `ReadLoggingStream` and `WriteLoggingStream`, but there is a simpler solution: `LoggingStream`. + +![LoggingStream](extras/images/Logger.svg) + +As usual, if your program is: + +```c++ +client.println("GET / HTTP/1.1"); +client.println("User-Agent: Arduino"); + +char response[256]; +client.readBytes(response, 256); +``` + +Then decorate `client` and replace the calls: + +```c++ +LoggingStream loggingClient(client, Serial); + +loggingClient.println("GET / HTTP/1.1"); +loggingClient.println("User-Agent: Arduino"); + +char response[256]; +loggingClient.readBytes(response, 256); +``` + +How to use error-correction codes (ECC)? +---------------------------------------- + +StreamUtils supports the [Hamming(7, 4)](https://en.wikipedia.org/wiki/Hamming(7,4)) error-correction code, which encodes 4 bits of data into 7 bits by adding three parity bits. +These extra bits increase the amount of traffic but allow correcting any one-bit error within the 7 bits. + +If you use this encoding on an 8-bit channel, it effectively doubles the amount of traffic. However, if you use an [`HardwareSerial`](https://www.arduino.cc/reference/en/language/functions/communication/serial/) instance (like `Serial`, `Serial1`...), you can slightly reduce the overhead by configuring the ports as a 7-bit channel, like so: + +```c++ +// Initialize serial port with 9600 bauds, 7-bits of data, no parity, and one stop bit +Serial1.begin(9600, SERIAL_7N1); +``` + +### Adding parity bits + +The class `HammingEncodingStream<7, 4>` decorates an existing `Stream` to include parity bits in every write operation. + +![HammingEncodingStream](extras/images/HammingEncodingStream.svg) + +You can use this class like so: + +```c++ +HammingEncodingStream<7, 4> eccSerial(Serial1); + +eccSerial.println("Hello world!"); +``` + +Like every `Stream` decorator in this library, `HammingEncodingStream<7, 4>` supports all `Stream` methods (like `print()`, `println()`, `read()`, `readBytes()`, and `available()`). + +### Correcting errors + +The class `HammingDecodingStream<7, 4>` decorates an existing `Stream` to decode parity bits in every read operation. + +![HammingDecodingStream](extras/images/HammingDecodingStream.svg) + +You can use this class like so: + +```c++ +HammingDecodingStream<7, 4> eccSerial(Serial1); + +char buffer[256]; +size_t n = eccSerial.readBytes(buffer, n); +``` + +Like every `Stream` decorator in this library, `HammingDecodingStream<7, 4>` supports all `Stream` methods (like `print()`, `println()`, `read()`, `readBytes()`, and `available()`). + +### Encoding and decoding in both directions + +The class `HammingStream<7, 4>` combines the features of `HammingEncodingStream<7, 4>` and `HammingDecodingStream<7, 4>`, which is very useful when you do a two-way communication. + +![HammingStream](extras/images/HammingStream.svg) + +You can use this class like so: + +```c++ +HammingStream<7, 4> eccSerial(Serial1); + +eccSerial.println("Hello world!"); + +char buffer[256]; +size_t n = eccSerial.readBytes(buffer, n); +``` + +Like every `Stream` decorator in this library, `HammingStream<7, 4>` supports all `Stream` methods (like `print()`, `println()`, `read()`, `readBytes()`, and `available()`). + + +How to retry write operations? +------------------------------ + +Sometimes, a stream is limited to the capacity of its internal buffer. In that case, you must wait before sending more data. +To solve this problem, StreamUtils provides the `WriteWaitingStream` decorator: + +![WriteWaitingStream](extras/images/WriteWaitingStream.svg) + +This function repeatedly waits and retries until it times out. +You can customize the `wait()` function; by default, it's [`yield()`](https://www.arduino.cc/en/Reference/SchedulerYield). + +For example, if you want to send more than 32 bytes with the [Wire library](https://www.arduino.cc/en/reference/wire), you can do: + +```c++ +WriteWaitingStream wireStream(Wire, [](){ + Wire.endTransmission(false); // <- don't forget this argument + Wire.beginTransmission(address); +}); + +Wire.beginTransmission(address); +wireStream.print("This is a very very long message that I'm sending!"); +Wire.endTransmission(); +``` + +As you can see, we use the `wait()` function as a hook to flush the Wire transmission buffer. Notice that we pass `false` to [`endTransmission()`](https://www.arduino.cc/en/Reference/WireEndTransmission) so that it sends the data but doesn't actually stop the transmission. + + +How to use a `String` as a stream? +--------------------- + +### Writing to a `String` + +Sometimes, you use a piece of code that expects a `Print` instance (like `ReadLoggingStream`), but you want the output in a `String` instead of a regular `Stream`. +In that case, use the `StringPrint` class. It wraps a `String` within a `Print` implementation. + +![StringPrint](extras/images/StringPrint.svg) + +Here is how you can use it: + +```c++ +StringPrint stream; + +stream.print("Temperature = "); +stream.print(22.3); +stream.print(" °C"); + +String result = stream.str(); +``` + +At the end of this snippet, the string `result` contains: + +``` +Temperature = 22.30 °C +``` + +### Reading from a `String` + +Similarly, there are cases where you have a `String`, but you need to pass a `Stream` to some other piece of code. In that case, use `StringStream`; it's similar to `StrintPrint`, except you can read as well. + +![StringStream](extras/images/StringStream.svg) + + +How to use EEPROM as a stream? +------------------------------ + +SteamUtils also allows using EEPROM as a stream. Simply create an instance of `EepromStream` and specify the start address and the size of the region you want to expose. + +![EepromStream](extras/images/EepromStream.svg) + +For example, it allows you to save a JSON document in EEPROM: + +```c++ +EepromStream eepromStream(0, 128); +serializeJson(doc, eepromStream); +eepromStream.flush(); // <- calls EEPROM.commit() on ESP (optional) +``` + +In the same way, you can read a JSON document from EEPROM: + +```c++ +EepromStream eepromStream(0, 128); +deserializeJson(doc, eepromStream); +``` + + +Summary +------- + +Some of the decorators are also available for the `Print` and `Client` classes. +See the equivalence table below. + +| Purpose | `Client` | `Stream` | `Print` | +|:-----------------------------------|:------------------------|:------------------------|:-----------------| +| Log *write* operations | `WriteLoggingClient` | `WriteLoggingStream` | `LoggingPrint` | +| Log *read* operations | `ReadLoggingClient` | `ReadLoggingStream` | | +| Log *read* and *write* op. | `LoggingClient` | `LoggingStream` | | +| Buffer *write* operations | `WriteBufferingClient` | `WriteBufferingStream` | `BufferingPrint` | +| Buffer *read* operations | `ReadBufferingClient` | `ReadBufferingStream` | | +| Repeat *write* operations | `WriteWaitingClient` | `WriteWaitingStream` | `WaitingPrint` | +| Use `String` as a stream | | `StringStream` | `StringPrint` | +| Use EEPROM as a stream | | `EepromStream` | | +| Error correction (decode only) | `HammingDecodingClient` | `HammingDecodingStream` | | +| Error correction (encode only) | `HammingEncodingClient` | `HammingEncodingStream` | `HammingPrint` | +| Error correction (encode & decode) | `HammingClient` | `HammingStream` | | + +When possible, prefer `ReadBufferingClient` to `ReadBufferingStream` because `Client::read()` often provides an optimized implementation. + + +Portability +----------- + +This library relies on the definitions of `Client`, `Print`, and `Stream`, which unfortunately differ from one core to another. + +It has been tested on the following cores: + +* [AVR](https://github.com/arduino/ArduinoCore-avr) +* [ESP32](https://github.com/espressif/arduino-esp32) +* [ESP8266](https://github.com/esp8266/Arduino) +* [nRF52](https://github.com/adafruit/Adafruit_nRF52_Arduino) +* [SAMD](https://github.com/arduino/ArduinoCore-samd) +* [STM32](https://github.com/stm32duino/Arduino_Core_STM32) +* [Teensy](https://github.com/PaulStoffregen/cores) + +If your core is not supported, please [open an issue](https://github.com/bblanchon/ArduinoStreamUtils/issues/new). +Thank you for your understanding. diff --git a/lib/ArduinoStreamUtils/examples/EepromRead/EepromRead.ino b/lib/ArduinoStreamUtils/examples/EepromRead/EepromRead.ino new file mode 100644 index 00000000..fb5ed409 --- /dev/null +++ b/lib/ArduinoStreamUtils/examples/EepromRead/EepromRead.ino @@ -0,0 +1,63 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License +// +// This example shows how to read from EEPROM + +#include + +void setup() { + // Initialize serial port + Serial.begin(9600); + while (!Serial) + continue; + +#if STREAMUTILS_ENABLE_EEPROM + +#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) + Serial.println("Initializing EEPROM..."); + EEPROM.begin(512); +#endif + + Serial.println("Make sure to run the EepromWrite example first!"); + + Serial.println("Reading EEPROM... "); + EepromStream s(0, 12); + Serial.print(s.readString()); + +#else + Serial.println("EepromStream is not supported on this platform. Sorry"); +#endif +} + +void loop() { + // not used in this example +} + +/***************************************************** + * * + * Love this project? * + * Star it on GitHub! * + * * + * .,,. * + * ,,:1. * + * ,.,:;1 * + * .,,,::;: * + * ,,,,::;;. * + * .,,,:::;;; * + * .....,,,,...,.,,,,,,:::,,,,,,,,,,,,, * + * ,,,,,,,,,,,:,...,,,,,,:::,,,,:::;;;11l * + * .;::::::::,,,,,,,,,,:::::,,::;;;1lt * + * .;;;:::,,,,,,,,::::::;:::;;1t: * + * :;;:,,,,,,::::::;;;;;;l1 * + * ,,,,:::::::;;;;;;l * + * .,,,,::::;;;;;;;:::: * + * ,,,,,:::;;;;;::,:::1 * + * ,,,,,::;;;t1:,,:::::;l * + * .,,,,:;;ll ;::::::;;, * + * ,,,:;ll. .1:::;;l * + * .,:lt, .1;;l: * + * * + * https://github.com/bblanchon/ArduinoStreamUtils * + * * + *****************************************************/ \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/examples/EepromWrite/EepromWrite.ino b/lib/ArduinoStreamUtils/examples/EepromWrite/EepromWrite.ino new file mode 100644 index 00000000..4e988268 --- /dev/null +++ b/lib/ArduinoStreamUtils/examples/EepromWrite/EepromWrite.ino @@ -0,0 +1,69 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License +// +// This example shows how to read from EEPROM + +#include + +void setup() { + // Initialize serial port + Serial.begin(9600); + while (!Serial) + continue; + +#if STREAMUTILS_ENABLE_EEPROM + +#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) + Serial.println("Initializing EEPROM..."); + EEPROM.begin(512); +#endif + + Serial.println("Writing to EEPROM..."); + EepromStream s(0, 12); + s.print("Hello World!"); + +#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) + Serial.println("Saving..."); + s.flush(); // only required on ESP +#endif + + Serial.println("Done!"); + Serial.println("Now, run the EepromRead example."); + +#else + Serial.println("EepromStream is not supported on this platform. Sorry"); +#endif +} + +void loop() { + // not used in this example +} + +/***************************************************** + * * + * Love this project? * + * Star it on GitHub! * + * * + * .,,. * + * ,,:1. * + * ,.,:;1 * + * .,,,::;: * + * ,,,,::;;. * + * .,,,:::;;; * + * .....,,,,...,.,,,,,,:::,,,,,,,,,,,,, * + * ,,,,,,,,,,,:,...,,,,,,:::,,,,:::;;;11l * + * .;::::::::,,,,,,,,,,:::::,,::;;;1lt * + * .;;;:::,,,,,,,,::::::;:::;;1t: * + * :;;:,,,,,,::::::;;;;;;l1 * + * ,,,,:::::::;;;;;;l * + * .,,,,::::;;;;;;;:::: * + * ,,,,,:::;;;;;::,:::1 * + * ,,,,,::;;;t1:,,:::::;l * + * .,,,,:;;ll ;::::::;;, * + * ,,,:;ll. .1:::;;l * + * .,:lt, .1;;l: * + * * + * https://github.com/bblanchon/ArduinoStreamUtils * + * * + *****************************************************/ \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/examples/HammingSerial1/HammingSerial1.ino b/lib/ArduinoStreamUtils/examples/HammingSerial1/HammingSerial1.ino new file mode 100644 index 00000000..16338a06 --- /dev/null +++ b/lib/ArduinoStreamUtils/examples/HammingSerial1/HammingSerial1.ino @@ -0,0 +1,68 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License +// +// This example shows how to use Hamming codes for error correction +// +// To run this program, you need two boards that support Serial1 with the same +// voltage. For example: Arduino Mega, Leonardo, or Nano Every (all 5V boards) +// +// Connect the TX pin of one board to the RX pin of the other and vice-versa. + +#include + +HammingStream<7, 4> eccSerial1(Serial1); + +void setup() { + Serial.begin(115200); + while (!Serial) + continue; + + Serial1.begin(9600); + while (!Serial) + continue; + + // Discard any remaining data in the input buffer + while (Serial1.available()) + Serial1.read(); +} + +void loop() { + // Did we receive something? + if (eccSerial1.available()) + // Print it + Serial.write(eccSerial1.read()); + + // Do we have something to send? + if (Serial.available()) + // Sent it + eccSerial1.write(Serial.read()); +} + +/***************************************************** + * * + * Love this project? * + * Star it on GitHub! * + * * + * .,,. * + * ,,:1. * + * ,.,:;1 * + * .,,,::;: * + * ,,,,::;;. * + * .,,,:::;;; * + * .....,,,,...,.,,,,,,:::,,,,,,,,,,,,, * + * ,,,,,,,,,,,:,...,,,,,,:::,,,,:::;;;11l * + * .;::::::::,,,,,,,,,,:::::,,::;;;1lt * + * .;;;:::,,,,,,,,::::::;:::;;1t: * + * :;;:,,,,,,::::::;;;;;;l1 * + * ,,,,:::::::;;;;;;l * + * .,,,,::::;;;;;;;:::: * + * ,,,,,:::;;;;;::,:::1 * + * ,,,,,::;;;t1:,,:::::;l * + * .,,,,:;;ll ;::::::;;, * + * ,,,:;ll. .1:::;;l * + * .,:lt, .1;;l: * + * * + * https://github.com/bblanchon/ArduinoStreamUtils * + * * + *****************************************************/ \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/examples/HammingSoftwareSerial/HammingSoftwareSerial.ino b/lib/ArduinoStreamUtils/examples/HammingSoftwareSerial/HammingSoftwareSerial.ino new file mode 100644 index 00000000..bf63bde0 --- /dev/null +++ b/lib/ArduinoStreamUtils/examples/HammingSoftwareSerial/HammingSoftwareSerial.ino @@ -0,0 +1,67 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License +// +// This example shows how to use Hamming codes for error correction +// +// To run this program, you need two boards connected like so: +// BOARD 1 - BOARD 2 +// pin 10 - pin 11 +// pin 11 - pin 10 +// +// SoftwareSerial is here for demonstration purposes; please don't use it in +// production, as it is notoriously unreliable. + +#include +#include + +SoftwareSerial linkSerial(10, 11); // RX, TX +HammingStream<7, 4> eccLinkSerial(linkSerial); + +void setup() { + Serial.begin(115200); + while (!Serial) + continue; + + linkSerial.begin(4800); +} + +void loop() { + // Did we receive something? + if (eccLinkSerial.available()) + // Print it + Serial.write(eccLinkSerial.read()); + + // Do we have something to send? + if (Serial.available()) + // Sent it + eccLinkSerial.write(Serial.read()); +} + +/***************************************************** + * * + * Love this project? * + * Star it on GitHub! * + * * + * .,,. * + * ,,:1. * + * ,.,:;1 * + * .,,,::;: * + * ,,,,::;;. * + * .,,,:::;;; * + * .....,,,,...,.,,,,,,:::,,,,,,,,,,,,, * + * ,,,,,,,,,,,:,...,,,,,,:::,,,,:::;;;11l * + * .;::::::::,,,,,,,,,,:::::,,::;;;1lt * + * .;;;:::,,,,,,,,::::::;:::;;1t: * + * :;;:,,,,,,::::::;;;;;;l1 * + * ,,,,:::::::;;;;;;l * + * .,,,,::::;;;;;;;:::: * + * ,,,,,:::;;;;;::,:::1 * + * ,,,,,::;;;t1:,,:::::;l * + * .,,,,:;;ll ;::::::;;, * + * ,,,:;ll. .1:::;;l * + * .,:lt, .1;;l: * + * * + * https://github.com/bblanchon/ArduinoStreamUtils * + * * + *****************************************************/ \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/examples/Logger/Logger.ino b/lib/ArduinoStreamUtils/examples/Logger/Logger.ino new file mode 100644 index 00000000..ccb483ed --- /dev/null +++ b/lib/ArduinoStreamUtils/examples/Logger/Logger.ino @@ -0,0 +1,61 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License +// +// This example shows how to log what goes through a Stream, both reads and +// writes operations. + +#include + +// Create a new stream that will forward all calls to Serial, and log to Serial. +// Everything will be written twice to the Serial! +LoggingStream loggingStream(Serial, Serial); + +void setup() { + // Initialize serial port + Serial.begin(9600); + while (!Serial) + continue; + + // Write to the serial port. + // Because loggingStream logs each write operation, the string will we written + // twice + loggingStream.println("Hello World!"); +} + +void loop() { + // Read from the serial port. + // Because loggingStream logs each read operation, everything we read is + // printed back to the serial port. + while (loggingStream.available()) { + loggingStream.read(); + } +} + +/***************************************************** + * * + * Love this project? * + * Star it on GitHub! * + * * + * .,,. * + * ,,:1. * + * ,.,:;1 * + * .,,,::;: * + * ,,,,::;;. * + * .,,,:::;;; * + * .....,,,,...,.,,,,,,:::,,,,,,,,,,,,, * + * ,,,,,,,,,,,:,...,,,,,,:::,,,,:::;;;11l * + * .;::::::::,,,,,,,,,,:::::,,::;;;1lt * + * .;;;:::,,,,,,,,::::::;:::;;1t: * + * :;;:,,,,,,::::::;;;;;;l1 * + * ,,,,:::::::;;;;;;l * + * .,,,,::::;;;;;;;:::: * + * ,,,,,:::;;;;;::,:::1 * + * ,,,,,::;;;t1:,,:::::;l * + * .,,,,:;;ll ;::::::;;, * + * ,,,:;ll. .1:::;;l * + * .,:lt, .1;;l: * + * * + * https://github.com/bblanchon/ArduinoStreamUtils * + * * + *****************************************************/ \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/examples/ReadBuffer/ReadBuffer.ino b/lib/ArduinoStreamUtils/examples/ReadBuffer/ReadBuffer.ino new file mode 100644 index 00000000..62e1aa4d --- /dev/null +++ b/lib/ArduinoStreamUtils/examples/ReadBuffer/ReadBuffer.ino @@ -0,0 +1,56 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License +// +// This example shows how to buffer the read operations of a stream. +// +// Like "echo," it reads from the serial port and prints back the same thing. +// What's interesting with this program is that it reads the input in chunks of +// 64 bytes, even if it seems to read them one by one. + +#include + +ReadBufferingStream bufferedSerial{Serial, 64}; + +void setup() { + // Initialize serial port + Serial.begin(9600); + while (!Serial) + continue; +} + +void loop() { + // Even if it looks like the bytes are extracted one by one, they are actually + // read by chunks of 64 bytes and placed in a buffer. + while (bufferedSerial.available()) { + Serial.write(bufferedSerial.read()); + } +} + +/***************************************************** + * * + * Love this project? * + * Star it on GitHub! * + * * + * .,,. * + * ,,:1. * + * ,.,:;1 * + * .,,,::;: * + * ,,,,::;;. * + * .,,,:::;;; * + * .....,,,,...,.,,,,,,:::,,,,,,,,,,,,, * + * ,,,,,,,,,,,:,...,,,,,,:::,,,,:::;;;11l * + * .;::::::::,,,,,,,,,,:::::,,::;;;1lt * + * .;;;:::,,,,,,,,::::::;:::;;1t: * + * :;;:,,,,,,::::::;;;;;;l1 * + * ,,,,:::::::;;;;;;l * + * .,,,,::::;;;;;;;:::: * + * ,,,,,:::;;;;;::,:::1 * + * ,,,,,::;;;t1:,,:::::;l * + * .,,,,:;;ll ;::::::;;, * + * ,,,:;ll. .1:::;;l * + * .,:lt, .1;;l: * + * * + * https://github.com/bblanchon/ArduinoStreamUtils * + * * + *****************************************************/ \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/examples/ReadLogger/ReadLogger.ino b/lib/ArduinoStreamUtils/examples/ReadLogger/ReadLogger.ino new file mode 100644 index 00000000..204ef9d3 --- /dev/null +++ b/lib/ArduinoStreamUtils/examples/ReadLogger/ReadLogger.ino @@ -0,0 +1,55 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License +// +// This example shows how to log what read from a Stream + +#include + +// Create a new stream that will forward all calls to Serial, and log to Serial. +// It will write back everything it reads, just like "echo" +ReadLoggingStream loggingStream(Serial, Serial); + +void setup() { + // Initialize serial port + Serial.begin(9600); + while (!Serial) + continue; +} + +void loop() { + // Read the serial port. + // Because loggingStream write everything it read, the program will show what + // you sent. + while (Serial.available()) { + loggingStream.write(Serial.read()); + } +} + +/***************************************************** + * * + * Love this project? * + * Star it on GitHub! * + * * + * .,,. * + * ,,:1. * + * ,.,:;1 * + * .,,,::;: * + * ,,,,::;;. * + * .,,,:::;;; * + * .....,,,,...,.,,,,,,:::,,,,,,,,,,,,, * + * ,,,,,,,,,,,:,...,,,,,,:::,,,,:::;;;11l * + * .;::::::::,,,,,,,,,,:::::,,::;;;1lt * + * .;;;:::,,,,,,,,::::::;:::;;1t: * + * :;;:,,,,,,::::::;;;;;;l1 * + * ,,,,:::::::;;;;;;l * + * .,,,,::::;;;;;;;:::: * + * ,,,,,:::;;;;;::,:::1 * + * ,,,,,::;;;t1:,,:::::;l * + * .,,,,:;;ll ;::::::;;, * + * ,,,:;ll. .1:::;;l * + * .,:lt, .1;;l: * + * * + * https://github.com/bblanchon/ArduinoStreamUtils * + * * + *****************************************************/ \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/examples/StringPrint/StringPrint.ino b/lib/ArduinoStreamUtils/examples/StringPrint/StringPrint.ino new file mode 100644 index 00000000..30b5eb46 --- /dev/null +++ b/lib/ArduinoStreamUtils/examples/StringPrint/StringPrint.ino @@ -0,0 +1,54 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License +// +// This example shows how to use StringPrint + +#include + +void setup() { + // Initialize serial port + Serial.begin(9600); + while (!Serial) + continue; + + StringPrint stream; + + stream.print("Temperature = "); + stream.print(22.3); + stream.print(" °C"); + + Serial.print(stream.str()); +} + +void loop() { + // no used in this example +} + +/***************************************************** + * * + * Love this project? * + * Star it on GitHub! * + * * + * .,,. * + * ,,:1. * + * ,.,:;1 * + * .,,,::;: * + * ,,,,::;;. * + * .,,,:::;;; * + * .....,,,,...,.,,,,,,:::,,,,,,,,,,,,, * + * ,,,,,,,,,,,:,...,,,,,,:::,,,,:::;;;11l * + * .;::::::::,,,,,,,,,,:::::,,::;;;1lt * + * .;;;:::,,,,,,,,::::::;:::;;1t: * + * :;;:,,,,,,::::::;;;;;;l1 * + * ,,,,:::::::;;;;;;l * + * .,,,,::::;;;;;;;:::: * + * ,,,,,:::;;;;;::,:::1 * + * ,,,,,::;;;t1:,,:::::;l * + * .,,,,:;;ll ;::::::;;, * + * ,,,:;ll. .1:::;;l * + * .,:lt, .1;;l: * + * * + * https://github.com/bblanchon/ArduinoStreamUtils * + * * + *****************************************************/ \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/examples/StringStream/StringStream.ino b/lib/ArduinoStreamUtils/examples/StringStream/StringStream.ino new file mode 100644 index 00000000..89d82c86 --- /dev/null +++ b/lib/ArduinoStreamUtils/examples/StringStream/StringStream.ino @@ -0,0 +1,55 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License +// +// This example shows how to use StringStream + +#include + +StringStream stream; + +void setup() { + // Initialize serial port + Serial.begin(9600); + while (!Serial) + continue; + + stream.print("Temperature = "); + stream.print(22.3); + stream.print(" °C"); +} + +void loop() { + if (stream.available() > 0) { + Serial.print((char)stream.read()); + } + delay(250); +} + +/***************************************************** + * * + * Love this project? * + * Star it on GitHub! * + * * + * .,,. * + * ,,:1. * + * ,.,:;1 * + * .,,,::;: * + * ,,,,::;;. * + * .,,,:::;;; * + * .....,,,,...,.,,,,,,:::,,,,,,,,,,,,, * + * ,,,,,,,,,,,:,...,,,,,,:::,,,,:::;;;11l * + * .;::::::::,,,,,,,,,,:::::,,::;;;1lt * + * .;;;:::,,,,,,,,::::::;:::;;1t: * + * :;;:,,,,,,::::::;;;;;;l1 * + * ,,,,:::::::;;;;;;l * + * .,,,,::::;;;;;;;:::: * + * ,,,,,:::;;;;;::,:::1 * + * ,,,,,::;;;t1:,,:::::;l * + * .,,,,:;;ll ;::::::;;, * + * ,,,:;ll. .1:::;;l * + * .,:lt, .1;;l: * + * * + * https://github.com/bblanchon/ArduinoStreamUtils * + * * + *****************************************************/ \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/examples/WriteBuffer/WriteBuffer.ino b/lib/ArduinoStreamUtils/examples/WriteBuffer/WriteBuffer.ino new file mode 100644 index 00000000..7e68487d --- /dev/null +++ b/lib/ArduinoStreamUtils/examples/WriteBuffer/WriteBuffer.ino @@ -0,0 +1,62 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License +// +// This example shows how to buffer writes to a stream. +// +// Like "echo," it reads from the serial port and prints back the same thing. +// What's interesting with this program is that it writes in chunks of +// 8 bytes, even if it seems to write them one by one. +// +// As you'll see, you need to type at least 8 bytes to see something. That's +// because it waits for the buffered to be full before sending it to the serial. + +#include + +// Create a new Stream that buffers all writes to Serial +WriteBufferingStream bufferedSerial{Serial, 8}; + +void setup() { + // Initialize serial port + Serial.begin(9600); + while (!Serial) + continue; + + Serial.println(F("Send at least 8 bytes to see the result")); +} + +void loop() { + // Even if it looks like the bytes are sent one by one, they are actual + // written in chunks of 8 bytes. + while (Serial.available()) { + bufferedSerial.write(Serial.read()); + } +} + +/***************************************************** + * * + * Love this project? * + * Star it on GitHub! * + * * + * .,,. * + * ,,:1. * + * ,.,:;1 * + * .,,,::;: * + * ,,,,::;;. * + * .,,,:::;;; * + * .....,,,,...,.,,,,,,:::,,,,,,,,,,,,, * + * ,,,,,,,,,,,:,...,,,,,,:::,,,,:::;;;11l * + * .;::::::::,,,,,,,,,,:::::,,::;;;1lt * + * .;;;:::,,,,,,,,::::::;:::;;1t: * + * :;;:,,,,,,::::::;;;;;;l1 * + * ,,,,:::::::;;;;;;l * + * .,,,,::::;;;;;;;:::: * + * ,,,,,:::;;;;;::,:::1 * + * ,,,,,::;;;t1:,,:::::;l * + * .,,,,:;;ll ;::::::;;, * + * ,,,:;ll. .1:::;;l * + * .,:lt, .1;;l: * + * * + * https://github.com/bblanchon/ArduinoStreamUtils * + * * + *****************************************************/ \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/examples/WriteLogger/WriteLogger.ino b/lib/ArduinoStreamUtils/examples/WriteLogger/WriteLogger.ino new file mode 100644 index 00000000..f08e4e40 --- /dev/null +++ b/lib/ArduinoStreamUtils/examples/WriteLogger/WriteLogger.ino @@ -0,0 +1,55 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License +// +// This example shows how to log what written to a Stream + +#include + +// Create a new stream that will forward all calls to Serial, and log to Serial. +// Everything will be written twice to the Serial! +WriteLoggingStream loggingStream(Serial, Serial); + +void setup() { + // Initialize serial port + Serial.begin(9600); + while (!Serial) + continue; + + // Write to the serial port. + // Because loggingStream logs each write operation, the string will we written + // twice + loggingStream.println("Hello World!"); +} + +void loop() { + // not used in this example +} + +/***************************************************** + * * + * Love this project? * + * Star it on GitHub! * + * * + * .,,. * + * ,,:1. * + * ,.,:;1 * + * .,,,::;: * + * ,,,,::;;. * + * .,,,:::;;; * + * .....,,,,...,.,,,,,,:::,,,,,,,,,,,,, * + * ,,,,,,,,,,,:,...,,,,,,:::,,,,:::;;;11l * + * .;::::::::,,,,,,,,,,:::::,,::;;;1lt * + * .;;;:::,,,,,,,,::::::;:::;;1t: * + * :;;:,,,,,,::::::;;;;;;l1 * + * ,,,,:::::::;;;;;;l * + * .,,,,::::;;;;;;;:::: * + * ,,,,,:::;;;;;::,:::1 * + * ,,,,,::;;;t1:,,:::::;l * + * .,,,,:;;ll ;::::::;;, * + * ,,,:;ll. .1:::;;l * + * .,:lt, .1;;l: * + * * + * https://github.com/bblanchon/ArduinoStreamUtils * + * * + *****************************************************/ \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/extras/images/EepromStream.svg b/lib/ArduinoStreamUtils/extras/images/EepromStream.svg new file mode 100644 index 00000000..efe2433e --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/images/EepromStream.svg @@ -0,0 +1 @@ +EEPROM \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/extras/images/HammingDecodingStream.svg b/lib/ArduinoStreamUtils/extras/images/HammingDecodingStream.svg new file mode 100644 index 00000000..1330fde7 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/images/HammingDecodingStream.svg @@ -0,0 +1 @@ +decoratedoriginalparity bits \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/extras/images/HammingEncodingStream.svg b/lib/ArduinoStreamUtils/extras/images/HammingEncodingStream.svg new file mode 100644 index 00000000..a2129454 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/images/HammingEncodingStream.svg @@ -0,0 +1 @@ +decoratedoriginalparity bits \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/extras/images/HammingStream.svg b/lib/ArduinoStreamUtils/extras/images/HammingStream.svg new file mode 100644 index 00000000..10471609 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/images/HammingStream.svg @@ -0,0 +1 @@ +decoratedoriginalparity bitsparity bits \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/extras/images/Logger.svg b/lib/ArduinoStreamUtils/extras/images/Logger.svg new file mode 100644 index 00000000..6be65429 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/images/Logger.svg @@ -0,0 +1 @@ +decoratedoriginallog \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/extras/images/ReadBuffer.svg b/lib/ArduinoStreamUtils/extras/images/ReadBuffer.svg new file mode 100644 index 00000000..b0913bb7 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/images/ReadBuffer.svg @@ -0,0 +1 @@ +decoratedoriginalbuffer \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/extras/images/ReadLogger.svg b/lib/ArduinoStreamUtils/extras/images/ReadLogger.svg new file mode 100644 index 00000000..f7ed4bc1 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/images/ReadLogger.svg @@ -0,0 +1 @@ +decoratedoriginallog \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/extras/images/StringPrint.svg b/lib/ArduinoStreamUtils/extras/images/StringPrint.svg new file mode 100644 index 00000000..345696b4 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/images/StringPrint.svg @@ -0,0 +1 @@ +String \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/extras/images/StringStream.svg b/lib/ArduinoStreamUtils/extras/images/StringStream.svg new file mode 100644 index 00000000..ecd0863a --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/images/StringStream.svg @@ -0,0 +1 @@ +String \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/extras/images/WriteBuffer.svg b/lib/ArduinoStreamUtils/extras/images/WriteBuffer.svg new file mode 100644 index 00000000..5b25c9d7 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/images/WriteBuffer.svg @@ -0,0 +1 @@ +decoratedoriginalbuffer \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/extras/images/WriteLogger.svg b/lib/ArduinoStreamUtils/extras/images/WriteLogger.svg new file mode 100644 index 00000000..a5b27242 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/images/WriteLogger.svg @@ -0,0 +1 @@ +decoratedoriginallog \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/extras/images/WriteWaitingStream.svg b/lib/ArduinoStreamUtils/extras/images/WriteWaitingStream.svg new file mode 100644 index 00000000..ca916f70 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/images/WriteWaitingStream.svg @@ -0,0 +1 @@ +decoratedoriginalwait() \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/extras/test/BufferingPrintTest.cpp b/lib/ArduinoStreamUtils/extras/test/BufferingPrintTest.cpp new file mode 100644 index 00000000..b6ba96a5 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/BufferingPrintTest.cpp @@ -0,0 +1,137 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#include "FailingAllocator.hpp" + +#include "StreamUtils/Prints/BufferingPrint.hpp" +#include "StreamUtils/Prints/SpyingPrint.hpp" +#include "StreamUtils/Prints/StringPrint.hpp" + +#include "doctest.h" + +using namespace StreamUtils; + +TEST_CASE("BufferingPrint") { + StringPrint target; + + StringPrint log; + SpyingPrint spy{target, log}; + + GIVEN("capacity is 4") { + BufferingPrint bufferingPrint{spy, 4}; + + SUBCASE("flush() calls write()") { + bufferingPrint.write("ABC", 3); + bufferingPrint.flush(); + +#if STREAMUTILS_PRINT_FLUSH_EXISTS + CHECK(log.str() == + "write('ABC', 3) -> 3" + "flush()"); +#else + CHECK(log.str() == "write('ABC', 3) -> 3"); +#endif + } + + GIVEN("the buffer is empty") { + SUBCASE("write(uint8_t)") { + int n = bufferingPrint.write('A'); + + CHECK(n == 1); + CHECK(log.str() == ""); + } + + SUBCASE("write(uint8_t) should flush") { + bufferingPrint.write('A'); + bufferingPrint.write('B'); + bufferingPrint.write('C'); + bufferingPrint.write('D'); + bufferingPrint.write('E'); + + CHECK(log.str() == "write('ABCD', 4) -> 4"); + } + + SUBCASE("write(char*,3) goes in buffer") { + size_t n = bufferingPrint.write("ABC", 3); + + CHECK(n == 3); + CHECK(log.str() == ""); + } + + SUBCASE("write(char*,4) bypasses buffer") { + size_t n = bufferingPrint.write("ABCD", 4); + + CHECK(n == 4); + CHECK(log.str() == "write('ABCD', 4) -> 4"); + } + SUBCASE("write(char*,2) bypasses buffer") { + size_t n = bufferingPrint.write("ABCD", 4); + + CHECK(n == 4); + CHECK(log.str() == "write('ABCD', 4) -> 4"); + } + } + + GIVEN("one byte in the buffer") { + bufferingPrint.write('A'); + + SUBCASE("write(char*,3) goes in buffer and flush") { + size_t n = bufferingPrint.write("BCD", 3); + + CHECK(n == 3); + CHECK(log.str() == "write('ABCD', 4) -> 4"); + } + + SUBCASE("write(char*,7) bypasses") { + size_t n = bufferingPrint.write("BCDEFGH", 7); + + CHECK(n == 7); + CHECK(log.str() == + "write('ABCD', 4) -> 4" + "write('EFGH', 4) -> 4"); + } + } + } + + GIVEN("capacity is 0") { + BufferingPrint bufferingPrint{spy, 0}; + + // SUBCASE("capacity()") { + // CHECK(bufferingPrint.capacity() == 0); + // } + + SUBCASE("write(uint8_t) forwards to target") { + int n = bufferingPrint.write('X'); + + CHECK(n == 1); + CHECK(log.str() == "write('X') -> 1"); + } + + SUBCASE("write(char*,1) forwards to target") { + int n = bufferingPrint.write("A", 1); + + CHECK(n == 1); + CHECK(log.str() == "write('A', 1) -> 1"); + } + + SUBCASE("flush()") { + bufferingPrint.flush(); + +#if STREAMUTILS_PRINT_FLUSH_EXISTS + CHECK(log.str() == "flush()"); +#else + CHECK(log.str() == ""); +#endif + } + } + + SUBCASE("Destructor should flush") { + { + BufferingPrint bufferingPrint{spy, 10}; + bufferingPrint.write("ABC", 3); + } + + CHECK(log.str() == "write('ABC', 3) -> 3"); + } +} diff --git a/lib/ArduinoStreamUtils/extras/test/CMakeLists.txt b/lib/ArduinoStreamUtils/extras/test/CMakeLists.txt new file mode 100644 index 00000000..3f49c108 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/CMakeLists.txt @@ -0,0 +1,104 @@ +# StreamUtils - github.com/bblanchon/ArduinoStreamUtils +# Copyright Benoit Blanchon 2019-2021 +# MIT License + +if(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Clang)") + add_compile_options(-Wall -Wextra -Werror -Wundef) +endif() + +set(SOURCES + BufferingPrintTest.cpp + EepromStreamTest.cpp + FailingAllocator.hpp + HammingClientTest.cpp + HammingDecodingClientTest.cpp + HammingDecodingStreamTest.cpp + HammingEncodingClientTest.cpp + HammingEncodingStreamTest.cpp + HammingPrintTest.cpp + HammingStreamTest.cpp + LoggingClientTest.cpp + LoggingPrintTest.cpp + LoggingStreamTest.cpp + MemoryStreamTest.cpp + ReadBufferingClientTest.cpp + ReadBufferingStreamTest.cpp + ReadLoggingClientTest.cpp + ReadLoggingStreamTest.cpp + ReadThrottlingStreamTest.cpp + StringPrintTest.cpp + StringStreamTest.cpp + WaitingPrintTest.cpp + WriteBufferingClientTest.cpp + WriteBufferingStreamTest.cpp + WriteLoggingClientTest.cpp + WriteLoggingStreamTest.cpp + WriteWaitingClientTest.cpp + WriteWaitingStreamTest.cpp + + doctest.h + main.cpp +) + +########## AVR ########## + +add_subdirectory(cores/avr) + +add_executable(StreamUtilsTestAvr ${SOURCES}) +target_link_libraries(StreamUtilsTestAvr AvrCore) + +add_test(Avr StreamUtilsTestAvr) + +########## ESP32 ########## + +add_subdirectory(cores/esp32) + +add_executable(StreamUtilsTestEsp32 ${SOURCES}) +target_link_libraries(StreamUtilsTestEsp32 Esp32Core) + +add_test(Esp32 StreamUtilsTestEsp32) + +########## ESP8266 ########## + +add_subdirectory(cores/esp8266) + +add_executable(StreamUtilsTestEsp8266 ${SOURCES}) +target_link_libraries(StreamUtilsTestEsp8266 Esp8266Core) + +add_test(Esp8266 StreamUtilsTestEsp8266) + +########## NRF52 ########## + +add_subdirectory(cores/nrf52) + +add_executable(StreamUtilsTestNrf52 ${SOURCES}) +target_link_libraries(StreamUtilsTestNrf52 Nrf52Core) + +add_test(Nrf52 StreamUtilsTestNrf52) + +########## SAMD ########## + +add_subdirectory(cores/samd) + +add_executable(StreamUtilsTestSamd ${SOURCES}) +target_link_libraries(StreamUtilsTestSamd SamdCore) + +add_test(Samd StreamUtilsTestSamd) + +########## STM32 ########## + +add_subdirectory(cores/stm32) + +add_executable(StreamUtilsTestStm32 ${SOURCES}) +target_link_libraries(StreamUtilsTestStm32 Stm32Core) + +add_test(Stm32 StreamUtilsTestStm32) + +########## Teensy ########## + +add_subdirectory(cores/teensy) + +add_executable(StreamUtilsTestTeensy ${SOURCES}) +target_link_libraries(StreamUtilsTestTeensy TeensyCore) + +add_test(Teensy StreamUtilsTestTeensy) \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/extras/test/EepromStreamTest.cpp b/lib/ArduinoStreamUtils/extras/test/EepromStreamTest.cpp new file mode 100644 index 00000000..78e943f0 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/EepromStreamTest.cpp @@ -0,0 +1,57 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#include "StreamUtils/Streams/EepromStream.hpp" + +#if STREAMUTILS_ENABLE_EEPROM + +#include "doctest.h" + +using namespace StreamUtils; + +TEST_CASE("EepromStream") { + SUBCASE("available()") { + EepromStream s(42, 84); + CHECK(s.available() == 84); + } + + SUBCASE("write(uint8_t)") { + EepromStream s(0, 2); + CHECK(s.write('a') == 1); + CHECK(s.write('b') == 1); + CHECK(s.write('c') == 0); + CHECK(s.write('d') == 0); + s.flush(); + CHECK(s.readString() == "ab"); + } + + SUBCASE("write(const uint8_t *, size_t)") { + EepromStream s(0, 5); + CHECK(s.write("abc", 3) == 3); + CHECK(s.write("def", 3) == 2); + CHECK(s.write("ghi", 3) == 0); + s.flush(); + CHECK(s.readString() == "abcde"); + } + + SUBCASE("read()") { + EepromStream s(0, 2); + s.write("ab", 2); + CHECK(s.read() == 'a'); + CHECK(s.read() == 'b'); + CHECK(s.read() == -1); + } + + SUBCASE("peek()") { + EepromStream s(0, 2); + s.write("ab", 2); + CHECK(s.peek() == 'a'); + CHECK(s.peek() == 'a'); + s.read(); + CHECK(s.peek() == 'b'); + s.read(); + CHECK(s.peek() == -1); + } +} +#endif \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/extras/test/FailingAllocator.hpp b/lib/ArduinoStreamUtils/extras/test/FailingAllocator.hpp new file mode 100644 index 00000000..64debc62 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/FailingAllocator.hpp @@ -0,0 +1,14 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include // size_t + +struct FailingAllocator { + void* allocate(size_t) { + return nullptr; + } + void deallocate(void*) {} +}; diff --git a/lib/ArduinoStreamUtils/extras/test/HammingClientTest.cpp b/lib/ArduinoStreamUtils/extras/test/HammingClientTest.cpp new file mode 100644 index 00000000..5182956d --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/HammingClientTest.cpp @@ -0,0 +1,30 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#include "SpyingAllocator.hpp" + +#include "StreamUtils/Clients/HammingClient.hpp" +#include "StreamUtils/Clients/MemoryClient.hpp" + +#include "doctest.h" + +using namespace StreamUtils; + +TEST_CASE("HammingClient") { + MemoryClient upstream(64); + + HammingClient<7, 4> client{upstream}; + + SUBCASE("read() decodes") { + upstream.print("Tq"); + + CHECK(client.read() == 'A'); + } + + SUBCASE("write() encodes") { + client.write('A'); + + CHECK(upstream.readString() == "Tq"); + } +} diff --git a/lib/ArduinoStreamUtils/extras/test/HammingDecodingClientTest.cpp b/lib/ArduinoStreamUtils/extras/test/HammingDecodingClientTest.cpp new file mode 100644 index 00000000..443bf7ae --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/HammingDecodingClientTest.cpp @@ -0,0 +1,137 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#include "SpyingAllocator.hpp" + +#include "StreamUtils/Clients/HammingDecodingClient.hpp" +#include "StreamUtils/Clients/MemoryClient.hpp" +#include "StreamUtils/Clients/SpyingClient.hpp" +#include "StreamUtils/Prints/StringPrint.hpp" + +#include "doctest.h" + +using namespace StreamUtils; + +TEST_CASE("HammingDecodingClient") { + MemoryClient upstream(64); + StringPrint log; + SpyingClient spy{upstream, log}; + SpyingAllocator allocator{log}; + + BasicHammingDecodingClient<7, 4, SpyingAllocator&> client{spy, allocator}; + + SUBCASE("read() with small buffer") { + uint8_t buffer[8]; + + SUBCASE("empty input") { + size_t result = client.read(buffer, sizeof(buffer)); + + CHECK(result == 0); +#if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL + CHECK(log.str() == "read(16) -> 0 [timeout]"); +#endif + } + + SUBCASE("decodes input") { + upstream.print("TqTb"); + + size_t result = client.read(buffer, sizeof(buffer)); + + CHECK(result == 2); + CHECK(std::string((char*)buffer, result) == "AB"); +#if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL + CHECK(log.str() == "read(16) -> 4 [timeout]"); +#endif + } + + SUBCASE("includes byte loaded by peek()") { + upstream.print("TqTb"); + client.peek(); + log.clear(); + + size_t result = client.read(buffer, sizeof(buffer)); + + CHECK(result == 2); + CHECK(std::string((char*)buffer, result) == "AB"); +#if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL + CHECK(log.str() == "read(15) -> 3 [timeout]"); +#endif + } + + SUBCASE("stores dangling byte") { + upstream.print("TqT"); + + size_t result = client.read(buffer, sizeof(buffer)); + + CHECK(result == 1); + CHECK(std::string((char*)buffer, result) == "A"); +#if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL + CHECK(log.str() == "read(16) -> 3 [timeout]"); +#endif + + log.clear(); + upstream.print("b"); + CHECK(client.peek() == 'B'); + CHECK(log.str() == "peek() -> 98"); + } + + SUBCASE("clears the byte loaded by peek") { + upstream.print("TqTb"); + client.peek(); + client.read(buffer, sizeof(buffer)); + client.peek(); + CHECK(log.str() == + "read() -> 84" + "peek() -> 113" + "read(15) -> 3 [timeout]" + "read() -> -1"); + } + } + + SUBCASE("read() with large buffer") { + uint8_t buffer[32]; + + SUBCASE("empty input") { + size_t result = client.read(buffer, sizeof(buffer)); + + CHECK(result == 0); +#if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL + CHECK(log.str() == + "allocate(64) -> ptr" + "read(64) -> 0 [timeout]" + "deallocate(ptr)"); +#endif + } + + SUBCASE("decodes input") { + upstream.print("TqTb"); + + size_t result = client.read(buffer, sizeof(buffer)); + + CHECK(result == 2); + CHECK(std::string((char*)buffer, result) == "AB"); +#if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL + CHECK(log.str() == + "allocate(64) -> ptr" + "read(64) -> 4 [timeout]" + "deallocate(ptr)"); +#endif + } + + SUBCASE("uses input buffer if allocation fails") { + upstream.print("TqTb"); + allocator.forceFail = true; + + size_t result = client.read(buffer, sizeof(buffer)); + + CHECK(result == 2); + CHECK(std::string((char*)buffer, result) == "AB"); +#if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL + CHECK(log.str() == + "allocate(64) -> null" + "read(32) -> 4 [timeout]"); +#endif + } + } +} diff --git a/lib/ArduinoStreamUtils/extras/test/HammingDecodingStreamTest.cpp b/lib/ArduinoStreamUtils/extras/test/HammingDecodingStreamTest.cpp new file mode 100644 index 00000000..0c60c0d3 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/HammingDecodingStreamTest.cpp @@ -0,0 +1,290 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#include "SpyingAllocator.hpp" + +#include "StreamUtils/Prints/StringPrint.hpp" +#include "StreamUtils/Streams/HammingDecodingStream.hpp" +#include "StreamUtils/Streams/SpyingStream.hpp" +#include "StreamUtils/Streams/StringStream.hpp" + +#include "doctest.h" + +using namespace StreamUtils; + +TEST_CASE("HammingDecodingStream") { + StringStream upstream; + StringPrint log; + SpyingStream spy{upstream, log}; + SpyingAllocator allocator{log}; + + BasicHammingDecodingStream<7, 4, SpyingAllocator&> stream{spy, allocator}; + + SUBCASE("available()") { + SUBCASE("empty input") { + CHECK(stream.available() == 0); + CHECK(log.str() == "available() -> 0"); + } + + SUBCASE("even number of bytes") { + upstream.print("ABCDEFGH"); + + CHECK(stream.available() == 4); + CHECK(log.str() == "available() -> 8"); + } + + SUBCASE("odd number of bytes") { + upstream.print("ABCDEFGHI"); + + CHECK(stream.available() == 4); + CHECK(log.str() == "available() -> 9"); + } + + SUBCASE("even number of bytes after peek") { + upstream.print("ABCDEFGH"); + stream.peek(); + log.clear(); + CHECK(stream.available() == 4); + CHECK(log.str() == "available() -> 7"); + } + + SUBCASE("odd number of bytes after peek") { + upstream.print("ABCDEFGHI"); + stream.peek(); + log.clear(); + CHECK(stream.available() == 4); + CHECK(log.str() == "available() -> 8"); + } + } + + SUBCASE("peek()") { + SUBCASE("returns -1 when empty") { + int result = stream.peek(); + + CHECK(result == -1); + CHECK(log.str() == "read() -> -1"); + } + + SUBCASE("returns -1 when only one byte in input") { + upstream.print("A"); + + int result = stream.peek(); + + CHECK(result == -1); + CHECK(log.str() == + "read() -> 65" + "peek() -> -1"); + } + + SUBCASE("returns decoded value") { + upstream.print("Tq"); + + int result = stream.peek(); + + CHECK(result == 'A'); + CHECK(log.str() == + "read() -> 84" + "peek() -> 113"); + } + + SUBCASE("doesn't call read() the second time") { + upstream.print("A"); + stream.peek(); + log.clear(); + + int result = stream.peek(); + + CHECK(result == -1); + CHECK(log.str() == "peek() -> -1"); + } + } + + SUBCASE("read()") { + SUBCASE("returns -1 if empty") { + int result = stream.read(); + + CHECK(result == -1); + CHECK(log.str() == "read() -> -1"); + } + + SUBCASE("returns -1 if only one byte in input") { + upstream.print("A"); + + int result = stream.read(); + + CHECK(result == -1); + CHECK(log.str() == + "read() -> 65" + "read() -> -1"); + } + + SUBCASE("returns decoded value if two bytes in input") { + upstream.print("Tq"); + + int result = stream.read(); + + CHECK(result == 'A'); + CHECK(log.str() == + "read() -> 84" + "read() -> 113"); + } + + SUBCASE("reuses the byte read by peek()") { + upstream.print("Tq"); + stream.peek(); + log.clear(); + + int result = stream.read(); + + CHECK(result == 'A'); + CHECK(log.str() == "read() -> 113"); + } + + SUBCASE("reuses the byte read by previous call") { + upstream.print("T"); + stream.read(); + upstream.print("q"); + log.clear(); + + int result = stream.read(); + + CHECK(result == 'A'); + CHECK(log.str() == "read() -> 113"); + } + + SUBCASE("flushes the buffered byte") { + upstream.print("TqTb"); + + CHECK(stream.read() == 'A'); + CHECK(stream.read() == 'B'); + + CHECK(log.str() == + "read() -> 84" + "read() -> 113" + "read() -> 84" + "read() -> 98"); + } + } + + SUBCASE("readBytes() with small buffer") { + char buffer[8]; + + SUBCASE("empty input") { + size_t result = stream.readBytes(buffer, sizeof(buffer)); + + CHECK(result == 0); +#if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL + CHECK(log.str() == "readBytes(16) -> 0 [timeout]"); +#endif + } + + SUBCASE("decodes input") { + upstream.print("TqTb"); + + size_t result = stream.readBytes(buffer, sizeof(buffer)); + + CHECK(result == 2); + CHECK(std::string(buffer, result) == "AB"); +#if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL + CHECK(log.str() == "readBytes(16) -> 4 [timeout]"); +#endif + } + + SUBCASE("includes byte loaded by peek()") { + upstream.print("TqTb"); + stream.peek(); + log.clear(); + + size_t result = stream.readBytes(buffer, sizeof(buffer)); + + CHECK(result == 2); + CHECK(std::string(buffer, result) == "AB"); +#if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL + CHECK(log.str() == "readBytes(15) -> 3 [timeout]"); +#endif + } + + SUBCASE("stores dangling byte") { + upstream.print("TqT"); + + size_t result = stream.readBytes(buffer, sizeof(buffer)); + + CHECK(result == 1); + CHECK(std::string(buffer, result) == "A"); +#if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL + CHECK(log.str() == "readBytes(16) -> 3 [timeout]"); +#endif + + log.clear(); + upstream.print("b"); + CHECK(stream.peek() == 'B'); + CHECK(log.str() == "peek() -> 98"); + } + + SUBCASE("clears the byte loaded by peek") { + upstream.print("TqTb"); + stream.peek(); + stream.readBytes(buffer, sizeof(buffer)); + CHECK(stream.peek() == -1); +#if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL + CHECK(log.str() == + "read() -> 84" + "peek() -> 113" + "readBytes(15) -> 3 [timeout]" + "read() -> -1"); +#endif + } + } + + SUBCASE("readBytes() with large buffer") { + char buffer[32]; + + SUBCASE("empty input") { + size_t result = stream.readBytes(buffer, sizeof(buffer)); + + CHECK(result == 0); +#if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL + CHECK(log.str() == + "allocate(64) -> ptr" + "readBytes(64) -> 0 [timeout]" + "deallocate(ptr)"); +#endif + } + + SUBCASE("decodes input") { + upstream.print("TqTb"); + + size_t result = stream.readBytes(buffer, sizeof(buffer)); + + CHECK(result == 2); + CHECK(std::string(buffer, result) == "AB"); +#if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL + CHECK(log.str() == + "allocate(64) -> ptr" + "readBytes(64) -> 4 [timeout]" + "deallocate(ptr)"); +#endif + } + + SUBCASE("uses input buffer if allocation fails") { + upstream.print("TqTb"); + allocator.forceFail = true; + + size_t result = stream.readBytes(buffer, sizeof(buffer)); + + CHECK(result == 2); + CHECK(std::string(buffer, result) == "AB"); +#if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL + CHECK(log.str() == + "allocate(64) -> null" + "readBytes(32) -> 4 [timeout]"); +#endif + } + } + + SUBCASE("flush()") { + stream.flush(); + CHECK(log.str() == "flush()"); + } +} diff --git a/lib/ArduinoStreamUtils/extras/test/HammingEncodingClientTest.cpp b/lib/ArduinoStreamUtils/extras/test/HammingEncodingClientTest.cpp new file mode 100644 index 00000000..1356d161 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/HammingEncodingClientTest.cpp @@ -0,0 +1,30 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#include "SpyingAllocator.hpp" + +#include "StreamUtils/Clients/HammingEncodingClient.hpp" +#include "StreamUtils/Clients/MemoryClient.hpp" + +#include "doctest.h" + +using namespace StreamUtils; + +TEST_CASE("HammingEncodingClient") { + MemoryClient upstream(64); + + HammingEncodingClient<7, 4> client{upstream}; + + SUBCASE("read() forwards upstream data") { + upstream.print("A"); + + CHECK(client.read() == 'A'); + } + + SUBCASE("write() encodes") { + client.write('A'); + + CHECK(upstream.readString() == "Tq"); + } +} diff --git a/lib/ArduinoStreamUtils/extras/test/HammingEncodingStreamTest.cpp b/lib/ArduinoStreamUtils/extras/test/HammingEncodingStreamTest.cpp new file mode 100644 index 00000000..faa51dce --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/HammingEncodingStreamTest.cpp @@ -0,0 +1,30 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#include "SpyingAllocator.hpp" + +#include "StreamUtils/Streams/HammingEncodingStream.hpp" +#include "StreamUtils/Streams/MemoryStream.hpp" + +#include "doctest.h" + +using namespace StreamUtils; + +TEST_CASE("HammingEncodingStream") { + MemoryStream upstream(64); + + HammingEncodingStream<7, 4> stream{upstream}; + + SUBCASE("read() forwards upstream data") { + upstream.print("A"); + + CHECK(stream.read() == 'A'); + } + + SUBCASE("write() encodes") { + stream.write('A'); + + CHECK(upstream.readString() == "Tq"); + } +} diff --git a/lib/ArduinoStreamUtils/extras/test/HammingPrintTest.cpp b/lib/ArduinoStreamUtils/extras/test/HammingPrintTest.cpp new file mode 100644 index 00000000..42d7556d --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/HammingPrintTest.cpp @@ -0,0 +1,202 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#include "SpyingAllocator.hpp" + +#include "StreamUtils/Prints/HammingPrint.hpp" +#include "StreamUtils/Prints/SpyingPrint.hpp" +#include "StreamUtils/Prints/StringPrint.hpp" +#include "StreamUtils/Streams/MemoryStream.hpp" + +#include "doctest.h" + +using namespace StreamUtils; + +TEST_CASE("HammingPrint") { + MemoryStream upstream(4); + + StringPrint log; + SpyingPrint spy{upstream, log}; + SpyingAllocator allocator{log}; + + BasicHammingPrint<7, 4, SpyingAllocator&> print{spy, allocator}; + + SUBCASE("write(char*, size_t)") { + SUBCASE("stops if even byte fails") { + CHECK(print.print("ABA") == 2); + print.write('A'); // try to flush remainder + + CHECK(log.str() == + "write('TqTbTq', 6) -> 4" + "write('T') -> 0" // no remainder + ); + } + + SUBCASE("saves remainder if odd byte fails") { + upstream.print("?"); + + CHECK(print.print("ABA") == 2); + print.write('A'); // try to flush remainder + + CHECK(log.str() == + "write('TqTbTq', 6) -> 3" + "write('b') -> 0" // remainder!! + ); + } + + SUBCASE("writes remainder first") { + upstream.print("???"); + print.write('A'); // add remainder + log.clear(); + upstream.readString(); + + CHECK(print.print("ABA") == 2); + + CHECK(log.str() == + "write('q') -> 1" + "write('TqTbTq', 6) -> 3"); + } + + SUBCASE("stops if remainder fails") { + upstream.print("???"); + print.write('A'); // add remainder + log.clear(); + + CHECK(print.print("ABA") == 0); + + CHECK(log.str() == "write('q') -> 0"); + } + + SUBCASE("allocates in stack when buffer is small") { + print.write("ABABABABABABABAB", 16); + + CHECK(log.str() == "write('TqTbTqTbTqTbTqTbTqTbTqTbTqTbTqTb', 32) -> 4"); + } + + SUBCASE("allocates in heap when buffer is large") { + print.write("ABABABABABABABABA", 17); + + CHECK(log.str() == + "allocate(34) -> ptr" + "write('TqTbTqTbTqTbTqTbTqTbTqTbTqTbTqTbTq', 34) -> 4" + "deallocate(ptr)"); + } + + SUBCASE("falls back to stack if heap fails") { + allocator.forceFail = true; + print.write("ABABABABABABABABA", 17); + + CHECK(log.str() == + "allocate(34) -> null" + "write('TqTbTqTbTqTbTqTbTqTbTqTbTqTbTqTb', 32) -> 4"); + } + } + + SUBCASE("write(char)") { + SUBCASE("writes both bytes") { + CHECK(print.write('A') == 1); + + CHECK(upstream.readString() == "Tq"); + CHECK(log.str() == + "write('T') -> 1" + "write('q') -> 1"); + } + + SUBCASE("stops if first by fails") { + upstream.print("????"); + + CHECK(print.write('A') == 0); + print.write('A'); // try to flush remainder + + CHECK(log.str() == + "write('T') -> 0" + "write('T') -> 0" // no remainder + ); + } + + SUBCASE("saves remainder if second by fails") { + upstream.print("???"); + + CHECK(print.write('A') == 1); + print.write('A'); // try to flush remainder + + CHECK(log.str() == + "write('T') -> 1" + "write('q') -> 0" + "write('q') -> 0" // remainder!! + ); + } + + SUBCASE("writes remainder first") { + upstream.print("???"); + print.write('A'); // add remainder + log.clear(); + upstream.readString(); + + CHECK(print.write('A') == 1); + + CHECK(log.str() == + "write('q') -> 1" + "write('T') -> 1" + "write('q') -> 1"); + } + + SUBCASE("stops if remainder fails") { + upstream.print("???"); + print.write('A'); // add remainder + log.clear(); + + CHECK(print.write('A') == 0); + print.write('A'); // try to flush remainder + + CHECK(log.str() == + "write('q') -> 0" + "write('q') -> 0" // remainder!! + ); + } + + SUBCASE("resets remainder") { + upstream.print("???"); + print.write('A'); // add remainder + log.clear(); + upstream.read(); + + CHECK(print.write('A') == 0); + print.write('A'); // try to flush remainder + + CHECK(log.str() == + "write('q') -> 1" + "write('T') -> 0" + "write('T') -> 0" // no remainder + ); + } + } + +#if STREAMUTILS_PRINT_FLUSH_EXISTS + SUBCASE("flush() writes remainder") { + upstream.print("???"); + print.write('A'); // add remainder + log.clear(); + upstream.read(); + + print.flush(); + + CHECK(log.str() == + "write('q') -> 1" + "flush()" // no remainder + ); + } +#endif + + SUBCASE("destructor flushes remainder") { + { + BasicHammingPrint<7, 4, SpyingAllocator&> print{spy, allocator}; + upstream.print("???"); + print.write('A'); // add remainder + log.clear(); + } + + CHECK(log.str() == "write('q') -> 0"); + } +} diff --git a/lib/ArduinoStreamUtils/extras/test/HammingStreamTest.cpp b/lib/ArduinoStreamUtils/extras/test/HammingStreamTest.cpp new file mode 100644 index 00000000..551f5fc8 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/HammingStreamTest.cpp @@ -0,0 +1,30 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#include "SpyingAllocator.hpp" + +#include "StreamUtils/Streams/HammingStream.hpp" +#include "StreamUtils/Streams/MemoryStream.hpp" + +#include "doctest.h" + +using namespace StreamUtils; + +TEST_CASE("HammingStream") { + MemoryStream upstream(64); + + HammingStream<7, 4> stream{upstream}; + + SUBCASE("read() decodes") { + upstream.print("Tq"); + + CHECK(stream.read() == 'A'); + } + + SUBCASE("write() encodes") { + stream.write('A'); + + CHECK(upstream.readString() == "Tq"); + } +} diff --git a/lib/ArduinoStreamUtils/extras/test/LoggingClientTest.cpp b/lib/ArduinoStreamUtils/extras/test/LoggingClientTest.cpp new file mode 100644 index 00000000..f457abea --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/LoggingClientTest.cpp @@ -0,0 +1,138 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#include "StreamUtils/Clients/LoggingClient.hpp" +#include "StreamUtils/Clients/MemoryClient.hpp" +#include "StreamUtils/Clients/SpyingClient.hpp" +#include "StreamUtils/Prints/StringPrint.hpp" + +#include "doctest.h" + +using namespace StreamUtils; + +TEST_CASE("LoggingClient") { + MemoryClient target(4); + + StringPrint log; + SpyingClient spy{target, log}; + + StringPrint output; + LoggingClient loggingClient{spy, output}; + + SUBCASE("available()") { + target.print("ABC"); + + size_t n = loggingClient.available(); + + CHECK(n == 3); + CHECK(log.str() == "available() -> 3"); + CHECK(output.str() == ""); + } + + SUBCASE("connect(IPAddress)") { + int n = loggingClient.connect(IPAddress("1.2.3.4"), 80); + + CHECK(n == 1); + CHECK(log.str() == "connect('1.2.3.4', 80) -> 1"); + CHECK(output.str() == ""); + } + + SUBCASE("connect(const char*)") { + int n = loggingClient.connect("1.2.3.4", 80); + + CHECK(n == 1); + CHECK(log.str() == "connect('1.2.3.4', 80) -> 1"); + CHECK(output.str() == ""); + } + + SUBCASE("connected()") { + uint8_t n = loggingClient.connected(); + + CHECK(n == false); + CHECK(log.str() == "connected() -> 0"); + CHECK(output.str() == ""); + } + + SUBCASE("stop()") { + loggingClient.stop(); + + CHECK(log.str() == "stop()"); + CHECK(output.str() == ""); + } + + SUBCASE("operator bool()") { + bool n = loggingClient.operator bool(); + + CHECK(n == true); + CHECK(log.str() == "operator bool() -> true"); + CHECK(output.str() == ""); + } + + SUBCASE("peek()") { + target.print("ABC"); + + int n = loggingClient.peek(); + + CHECK(n == 'A'); + CHECK(log.str() == "peek() -> 65"); + CHECK(output.str() == ""); + } + + SUBCASE("read()") { + target.print("ABC"); + + int n = loggingClient.read(); + + CHECK(n == 'A'); + CHECK(log.str() == "read() -> 65"); + CHECK(output.str() == "A"); + } + + SUBCASE("read(uint8_t*,size_t)") { + target.print("ABC"); + + uint8_t s[4] = {0}; + size_t n = loggingClient.read(s, 4); + + CHECK(n == 3); + CHECK(log.str() == "read(4) -> 3 [timeout]"); + CHECK(output.str() == "ABC"); + } + + SUBCASE("readBytes()") { + target.print("ABC"); + + char s[4] = {0}; + size_t n = loggingClient.readBytes(s, 4); + + CHECK(n == 3); +#if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL + CHECK(log.str() == "readBytes(4) -> 3 [timeout]"); +#endif + CHECK(output.str() == "ABC"); + } + + SUBCASE("write(char)") { + int n = loggingClient.write('A'); + + CHECK(n == 1); + CHECK(log.str() == "write('A') -> 1"); + CHECK(output.str() == "A"); + } + + SUBCASE("write(char*,size_t)") { + int n = loggingClient.write("ABCDEF", 6); + + CHECK(n == 4); + CHECK(log.str() == "write('ABCDEF', 6) -> 4"); + CHECK(output.str() == "ABCD"); + } + + SUBCASE("flush()") { + loggingClient.flush(); + + CHECK(output.str() == ""); + CHECK(log.str() == "flush()"); + } +} diff --git a/lib/ArduinoStreamUtils/extras/test/LoggingPrintTest.cpp b/lib/ArduinoStreamUtils/extras/test/LoggingPrintTest.cpp new file mode 100644 index 00000000..ca20c483 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/LoggingPrintTest.cpp @@ -0,0 +1,41 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#include "StreamUtils/Prints/LoggingPrint.hpp" +#include "StreamUtils/Streams/MemoryStream.hpp" + +#include "doctest.h" + +using namespace StreamUtils; + +TEST_CASE("LoggingPrint") { + MemoryStream primary(4); + MemoryStream secondary(64); + LoggingPrint loggingPrint{primary, secondary}; + + SUBCASE("write(char)") { + int n = loggingPrint.write('A'); + + CHECK(n == 1); + CHECK(primary.readString() == "A"); + CHECK(secondary.readString() == "A"); + } + + SUBCASE("write(char*,size_t)") { + int n = loggingPrint.write("ABCDEF", 6); + + CHECK(n == 4); + CHECK(primary.readString() == "ABCD"); + CHECK(secondary.readString() == "ABCD"); + } + +#if STREAMUTILS_PRINT_FLUSH_EXISTS + SUBCASE("flush()") { + loggingPrint.write("AB", 2); + REQUIRE(primary.available() == 2); + loggingPrint.flush(); + REQUIRE(primary.available() == 0); + } +#endif +} diff --git a/lib/ArduinoStreamUtils/extras/test/LoggingStreamTest.cpp b/lib/ArduinoStreamUtils/extras/test/LoggingStreamTest.cpp new file mode 100644 index 00000000..aab8537b --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/LoggingStreamTest.cpp @@ -0,0 +1,93 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#include "StreamUtils/Prints/StringPrint.hpp" +#include "StreamUtils/Streams/LoggingStream.hpp" +#include "StreamUtils/Streams/MemoryStream.hpp" +#include "StreamUtils/Streams/SpyingStream.hpp" + +#include "doctest.h" + +using namespace StreamUtils; + +TEST_CASE("LoggingStream") { + MemoryStream upstream{4}; + StringPrint output; + + StringPrint log; + SpyingStream upstreamSpy{upstream, log}; + + LoggingStream loggingStream{upstreamSpy, output}; + + // upstream -> upstreamSpy -> loggingStream -> output + // | + // v + // log + + SUBCASE("available()") { + upstream.print("ABC"); + + size_t n = loggingStream.available(); + + CHECK(n == 3); + CHECK(log.str() == "available() -> 3"); + CHECK(output.str() == ""); + } + + SUBCASE("peek()") { + upstream.print("ABC"); + + int n = loggingStream.peek(); + + CHECK(n == 'A'); + CHECK(log.str() == "peek() -> 65"); + CHECK(output.str() == ""); + } + + SUBCASE("read()") { + upstream.print("ABC"); + + int n = loggingStream.read(); + + CHECK(n == 'A'); + CHECK(log.str() == "read() -> 65"); + CHECK(output.str() == "A"); + } + + SUBCASE("readBytes()") { + upstream.print("ABC"); + + char s[4] = {0}; + size_t n = loggingStream.readBytes(s, 4); + + CHECK(n == 3); +#if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL + CHECK(log.str() == "readBytes(4) -> 3 [timeout]"); +#endif + CHECK(output.str() == "ABC"); + } + + SUBCASE("write(char)") { + int n = loggingStream.write('A'); + + CHECK(n == 1); + CHECK(log.str() == "write('A') -> 1"); + CHECK(output.str() == "A"); + } + + SUBCASE("write(char*,size_t)") { + int n = loggingStream.write("ABCDEF", 6); + + CHECK(n == 4); + CHECK(log.str() == "write('ABCDEF', 6) -> 4"); + CHECK(output.str() == "ABCD"); + } + + SUBCASE("flush()") { + loggingStream.flush(); + + CHECK(log.str() == "flush()"); + CHECK(output.str() == ""); + } +} diff --git a/lib/ArduinoStreamUtils/extras/test/MemoryStreamTest.cpp b/lib/ArduinoStreamUtils/extras/test/MemoryStreamTest.cpp new file mode 100644 index 00000000..a649d1fc --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/MemoryStreamTest.cpp @@ -0,0 +1,116 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#include "StreamUtils/Streams/MemoryStream.hpp" +#include "StreamUtils/Streams/SpyingStream.hpp" + +#include "doctest.h" + +using namespace StreamUtils; + +TEST_CASE("MemoryStream") { + MemoryStream stream(4); + + WHEN("stream is empty") { + THEN("available() return 0") { + CHECK(stream.available() == 0); + } + + THEN("write(uint8) returns 1") { + CHECK(stream.write('A') == 1); + } + + THEN("write(\"ABCD\",4) returns 4") { + CHECK(stream.write("ABCD", 4) == 4); + } + + THEN("write(uint8*,8) returns 4") { + CHECK(stream.write("ABCDEFGH", 8) == 4); + } + + THEN("read() return -1") { + CHECK(stream.read() == -1); + } + + THEN("peek() return -1") { + CHECK(stream.peek() == -1); + } + } + + WHEN("stream is full") { + stream.print("ABCD"); + + THEN("available() return 4") { + CHECK(stream.available() == 4); + } + + THEN("write(uint8) returns 0") { + CHECK(stream.write('A') == 0); + } + + THEN("write(\"ABCD\",4) returns 0") { + CHECK(stream.write("ABCD", 4) == 0); + } + + THEN("read() returns the next value") { + CHECK(stream.read() == 'A'); + CHECK(stream.read() == 'B'); + } + + THEN("read(uint8_t*,size_t) extracts the next bytes") { + char data[5] = {0}; + CHECK(stream.readBytes(data, 4) == 4); + CHECK(data == std::string("ABCD")); + } + + THEN("peek() returns the first value") { + CHECK(stream.peek() == 'A'); + CHECK(stream.peek() == 'A'); + } + + SUBCASE("read() makes room for one byte") { + stream.read(); // make room + REQUIRE(stream.available() == 3); + + stream.write('E'); // write at the beginning + REQUIRE(stream.available() == 4); + + char data[5] = {0}; + stream.readBytes(data, 4); + CHECK(data == std::string("BCDE")); + } + } + + WHEN("lower half is filled") { + stream.print("AB"); + + THEN("available() return 2") { + CHECK(stream.available() == 2); + } + + THEN("write(uint8) returns 1") { + CHECK(stream.write('C') == 1); + } + + THEN("write(\"ABCD\",4) returns 2") { + CHECK(stream.write("ABCD", 4) == 2); + } + + THEN("read() returns the next value") { + CHECK(stream.read() == 'A'); + CHECK(stream.read() == 'B'); + } + + THEN("read(uint8_t*,size_t) extracts the next bytes") { + char data[5] = {0}; + CHECK(stream.readBytes(data, 4) == 2); + CHECK(data == std::string("AB")); + } + + THEN("peek() returns the first value") { + CHECK(stream.peek() == 'A'); + CHECK(stream.peek() == 'A'); + } + } +} diff --git a/lib/ArduinoStreamUtils/extras/test/ReadBufferingClientTest.cpp b/lib/ArduinoStreamUtils/extras/test/ReadBufferingClientTest.cpp new file mode 100644 index 00000000..37d965ba --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/ReadBufferingClientTest.cpp @@ -0,0 +1,337 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#include "FailingAllocator.hpp" + +#include "StreamUtils/Clients/MemoryClient.hpp" +#include "StreamUtils/Clients/ReadBufferingClient.hpp" +#include "StreamUtils/Clients/SpyingClient.hpp" +#include "StreamUtils/Prints/StringPrint.hpp" + +#include "doctest.h" + +using namespace StreamUtils; + +TEST_CASE("ReadBufferingClient") { + MemoryClient target(64); + StringPrint log; + SpyingClient spy{target, log}; + + SUBCASE("capacity = 4") { + ReadBufferingClient bufferedClient{spy, 4}; + Client& client = bufferedClient; + + SUBCASE("available()") { + target.print("ABCDEFGH"); + + SUBCASE("empty input") { + target.flush(); + CHECK(client.available() == 0); + CHECK(log.str() == "available() -> 0"); + } + + SUBCASE("read empty input") { + target.flush(); + + client.read(); + + CHECK(client.available() == 0); + CHECK(log.str() == + "available() -> 0" + "read() -> -1" + "available() -> 0"); + } + + SUBCASE("same a target") { + CHECK(client.available() == 8); + CHECK(log.str() == "available() -> 8"); + } + + SUBCASE("target + in buffer") { + client.read(); + + CHECK(client.available() == 7); + CHECK(log.str() == + "available() -> 8" + "read(4) -> 4" + "available() -> 4"); + } + } + + SUBCASE("peek()") { + SUBCASE("returns -1 when empty") { + target.flush(); + + int result = client.peek(); + + CHECK(result == -1); + CHECK(log.str() == "peek() -> -1"); + } + + SUBCASE("doesn't call readBytes() when buffer is empty") { + target.print("A"); + + int result = client.peek(); + + CHECK(result == 'A'); + CHECK(log.str() == "peek() -> 65"); + } + + SUBCASE("doesn't call peek() when buffer is full") { + target.print("AB"); + + client.read(); + int result = client.peek(); + + CHECK(result == 'B'); + CHECK(log.str() == + "available() -> 2" + "read(2) -> 2"); + } + } + + SUBCASE("read()") { + SUBCASE("reads 4 bytes at a time") { + target.print("ABCDEFG"); + std::string result; + + for (int i = 0; i < 7; i++) { + result += (char)client.read(); + } + + CHECK(result == "ABCDEFG"); + CHECK(log.str() == + "available() -> 7" + "read(4) -> 4" + "available() -> 3" + "read(3) -> 3"); + } + + SUBCASE("returns -1 when empty") { + target.flush(); + + int result = client.read(); + + CHECK(result == -1); + CHECK(log.str() == + "available() -> 0" + "read() -> -1"); + } + } + + SUBCASE("read()") { + SUBCASE("empty input") { + target.flush(); + + char c; + size_t result = client.readBytes(&c, 1); + + CHECK(result == 0); + +#if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL + CHECK(log.str() == + "available() -> 0" + "readBytes(1) -> 0 [timeout]"); +#else + CHECK(log.str() == + "available() -> 0" + "read() -> -1"); // [timeout] from timedRead() +#endif + } + + SUBCASE("reads 4 bytes when requested one") { + target.print("ABCDEFG"); + + char c; + size_t result = client.readBytes(&c, 1); + + CHECK(c == 'A'); + CHECK(result == 1); +#if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL + CHECK(log.str() == + "available() -> 7" + "readBytes(4) -> 4"); +#else + CHECK(log.str() == + "available() -> 7" + "read(4) -> 4"); +#endif + } + + SUBCASE("copy one byte from buffer") { + target.print("ABCDEFGH"); + client.read(); // load buffer + + char c; + size_t result = client.readBytes(&c, 1); + + CHECK(c == 'B'); + CHECK(result == 1); + CHECK(log.str() == + "available() -> 8" + "read(4) -> 4"); + } + + SUBCASE("copy content from buffer then bypass buffer") { + target.print("ABCDEFGH"); + client.read(); // load buffer + + char c[8] = {0}; + size_t result = client.readBytes(c, 7); + + CHECK(c == std::string("BCDEFGH")); + CHECK(result == 7); +#if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL + CHECK(log.str() == + "available() -> 8" + "read(4) -> 4" + "available() -> 4" + "readBytes(4) -> 4"); +#else + CHECK(log.str() == + "available() -> 8" + "read(4) -> 4" + "available() -> 4" + "read(4) -> 4"); +#endif + } + + SUBCASE("copy content from buffer twice") { + target.print("ABCDEFGH"); + client.read(); // load buffer + + char c[8] = {0}; + size_t result = client.readBytes(c, 4); + + CHECK(c == std::string("BCDE")); + CHECK(result == 4); +#if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL + CHECK(log.str() == + "available() -> 8" + "read(4) -> 4" + "available() -> 4" + "readBytes(4) -> 4"); +#else + CHECK(log.str() == + "available() -> 8" + "read(4) -> 4" + "available() -> 4" + "read(4) -> 4"); +#endif + } + + SUBCASE("read past the end") { + target.print("A"); + + char c; + client.readBytes(&c, 1); + size_t result = client.readBytes(&c, 1); + + CHECK(result == 0); + +#if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL + CHECK(log.str() == + "available() -> 1" + "readBytes(1) -> 1" + "available() -> 0" + "readBytes(1) -> 0 [timeout]"); +#else + CHECK(log.str() == + "available() -> 1" + "read() -> 65" + "available() -> 0" + "read() -> -1"); // [timeout] from timedRead() +#endif + } + } + + SUBCASE("flush()") { + client.flush(); + CHECK(log.str() == "flush()"); + } + + SUBCASE("copy constructor") { + target.print("ABCDEFGH"); + bufferedClient.read(); + + auto dup = bufferedClient; + + int result = dup.read(); + + CHECK(result == 'B'); + CHECK(log.str() == + "available() -> 8" + "read(4) -> 4"); + } + } + + SUBCASE("No memory") { + BasicReadBufferingClient client(spy, 4); + + SUBCASE("available()") { + target.print("ABC"); + + CHECK(client.available() == 3); + } + + // SUBCASE("capacity()") { + // CHECK(client.capacity() == 0); + // } + + SUBCASE("peek()") { + target.print("ABC"); + + int c = client.peek(); + + CHECK(c == 'A'); + CHECK(log.str() == "peek() -> 65"); + } + + SUBCASE("read()") { + target.print("ABC"); + + int c = client.read(); + + CHECK(c == 'A'); + CHECK(log.str() == "read() -> 65"); + } + + SUBCASE("readBytes()") { + target.print("ABC"); + + char s[4] = {0}; + int n = client.readBytes(s, 3); + + CHECK(n == 3); + CHECK(s == std::string("ABC")); +#if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL + CHECK(log.str() == "readBytes(3) -> 3"); +#endif + } + } + + SUBCASE("Real example") { + ReadBufferingClient bufferedClient{spy, 64}; + Client& client = bufferedClient; + + target.print("{\"helloWorld\":\"Hello World\"}"); + + char c[] = "ABCDEFGH"; + CHECK(client.readBytes(&c[0], 1) == 1); + CHECK(client.readBytes(&c[1], 1) == 1); + CHECK(client.readBytes(&c[2], 1) == 1); + CHECK(client.readBytes(&c[3], 1) == 1); + + CHECK(c == std::string("{\"heEFGH")); +#if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL + CHECK(log.str() == + "available() -> 28" + "readBytes(28) -> 28"); +#else + CHECK(log.str() == + "available() -> 28" + "read(28) -> 28"); +#endif + } +} diff --git a/lib/ArduinoStreamUtils/extras/test/ReadBufferingStreamTest.cpp b/lib/ArduinoStreamUtils/extras/test/ReadBufferingStreamTest.cpp new file mode 100644 index 00000000..bbc13b73 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/ReadBufferingStreamTest.cpp @@ -0,0 +1,318 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#include "FailingAllocator.hpp" + +#include "StreamUtils/Prints/StringPrint.hpp" +#include "StreamUtils/Streams/ReadBufferingStream.hpp" +#include "StreamUtils/Streams/SpyingStream.hpp" +#include "StreamUtils/Streams/StringStream.hpp" + +#include "doctest.h" + +using namespace StreamUtils; + +TEST_CASE("ReadBufferingStream") { + StringStream upstream; + StringPrint log; + SpyingStream spy{upstream, log}; + + SUBCASE("capacity = 4") { + ReadBufferingStream bufferedStream{spy, 4}; + Stream& stream = bufferedStream; + + SUBCASE("available()") { + SUBCASE("empty input") { + CHECK(stream.available() == 0); + CHECK(log.str() == "available() -> 0"); + } + + SUBCASE("read empty input") { + int n = stream.read(); + + CHECK(n == -1); + CHECK(stream.available() == 0); + CHECK(log.str() == + "available() -> 0" + "read() -> -1" + "available() -> 0"); + } + + SUBCASE("same a upstream") { + upstream.print("ABCDEFGH"); + + CHECK(stream.available() == 8); + CHECK(log.str() == "available() -> 8"); + } + + SUBCASE("upstream + in buffer") { + upstream.print("ABCDEFGH"); + + int n = stream.read(); + + CHECK(n == 'A'); + CHECK(stream.available() == 7); +#if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL + CHECK(log.str() == + "available() -> 8" + "readBytes(4) -> 4" + "available() -> 4"); +#endif + } + } + + SUBCASE("peek()") { + SUBCASE("returns -1 when empty") { + upstream.flush(); + + int result = stream.peek(); + + CHECK(result == -1); + CHECK(log.str() == "peek() -> -1"); + } + + SUBCASE("doesn't call readBytes() when buffer is empty") { + upstream.print("A"); + + int result = stream.peek(); + + CHECK(result == 'A'); + CHECK(log.str() == "peek() -> 65"); + } + + SUBCASE("doesn't call peek() when buffer is full") { + upstream.print("AB"); + + stream.read(); + int result = stream.peek(); + + CHECK(result == 'B'); +#if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL + CHECK(log.str() == + "available() -> 2" + "readBytes(2) -> 2"); +#endif + } + } + + SUBCASE("read()") { + SUBCASE("reads 4 bytes at a time") { + upstream.print("ABCDEFG"); + std::string result; + + for (int i = 0; i < 7; i++) { + result += (char)stream.read(); + } + + CHECK(result == "ABCDEFG"); +#if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL + CHECK(log.str() == + "available() -> 7" + "readBytes(4) -> 4" + "available() -> 3" + "readBytes(3) -> 3"); +#endif + } + + SUBCASE("returns -1 when empty") { + upstream.flush(); + + int result = stream.read(); + + CHECK(result == -1); +#if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL + CHECK(log.str() == + "available() -> 0" + "read() -> -1"); +#endif + } + } + + SUBCASE("readBytes()") { + SUBCASE("empty input") { + upstream.flush(); + + char c; + size_t result = stream.readBytes(&c, 1); + + CHECK(result == 0); +#if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL + CHECK(log.str() == + "available() -> 0" + "readBytes(1) -> 0 [timeout]"); +#endif + } + + SUBCASE("reads 4 bytes when requested one") { + upstream.print("ABCDEFG"); + + char c; + size_t result = stream.readBytes(&c, 1); + + CHECK(c == 'A'); + CHECK(result == 1); +#if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL + CHECK(log.str() == + "available() -> 7" + "readBytes(4) -> 4"); +#endif + } + + SUBCASE("copy one byte from buffer") { + upstream.print("ABCDEFGH"); + stream.read(); // load buffer + + char c; + size_t result = stream.readBytes(&c, 1); + + CHECK(c == 'B'); + CHECK(result == 1); +#if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL + CHECK(log.str() == + "available() -> 8" + "readBytes(4) -> 4"); +#endif + } + + SUBCASE("copy content from buffer then bypass buffer") { + upstream.print("ABCDEFGH"); + stream.read(); // load buffer + + char c[8] = {0}; + size_t result = stream.readBytes(c, 7); + + CHECK(c == std::string("BCDEFGH")); + CHECK(result == 7); +#if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL + CHECK(log.str() == + "available() -> 8" + "readBytes(4) -> 4" + "available() -> 4" + "readBytes(4) -> 4"); +#endif + } + + SUBCASE("copy content from buffer twice") { + upstream.print("ABCDEFGH"); + stream.read(); // load buffer + + char c[8] = {0}; + size_t result = stream.readBytes(c, 4); + + CHECK(c == std::string("BCDE")); + CHECK(result == 4); +#if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL + CHECK(log.str() == + "available() -> 8" + "readBytes(4) -> 4" + "available() -> 4" + "readBytes(4) -> 4"); +#endif + } + + SUBCASE("read past the end") { + upstream.print("A"); + + char c; + stream.readBytes(&c, 1); + size_t result = stream.readBytes(&c, 1); + + CHECK(result == 0); +#if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL + CHECK(log.str() == + "available() -> 1" + "readBytes(1) -> 1" + "available() -> 0" + "readBytes(1) -> 0 [timeout]"); +#endif + } + } + + SUBCASE("flush()") { + stream.flush(); + CHECK(log.str() == "flush()"); + } + + SUBCASE("copy constructor") { + upstream.print("ABCDEFGH"); + bufferedStream.read(); + + auto dup = bufferedStream; + + int result = dup.read(); + + CHECK(result == 'B'); +#if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL + CHECK(log.str() == + "available() -> 8" + "readBytes(4) -> 4"); +#endif + } + } + + SUBCASE("No memory") { + BasicReadBufferingStream stream(spy, 4); + + SUBCASE("available()") { + upstream.print("ABC"); + + CHECK(stream.available() == 3); + } + + // SUBCASE("capacity()") { + // CHECK(stream.capacity() == 0); + // } + + SUBCASE("peek()") { + upstream.print("ABC"); + + int c = stream.peek(); + + CHECK(c == 'A'); + CHECK(log.str() == "peek() -> 65"); + } + + SUBCASE("read()") { + upstream.print("ABC"); + + int c = stream.read(); + + CHECK(c == 'A'); + CHECK(log.str() == "read() -> 65"); + } + + SUBCASE("readBytes()") { + upstream.print("ABC"); + + char s[4] = {0}; + int n = stream.readBytes(s, 3); + + CHECK(n == 3); + CHECK(s == std::string("ABC")); +#if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL + CHECK(log.str() == "readBytes(3) -> 3"); +#endif + } + } + + SUBCASE("Real example") { + ReadBufferingStream bufferedStream{spy, 64}; + Stream& stream = bufferedStream; + + upstream.print("{\"helloWorld\":\"Hello World\"}"); + + char c[] = "ABCDEFGH"; + CHECK(stream.readBytes(&c[0], 1) == 1); + CHECK(stream.readBytes(&c[1], 1) == 1); + CHECK(stream.readBytes(&c[2], 1) == 1); + CHECK(stream.readBytes(&c[3], 1) == 1); + + CHECK(c == std::string("{\"heEFGH")); +#if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL + CHECK(log.str() == + "available() -> 28" + "readBytes(28) -> 28"); +#endif + } +} diff --git a/lib/ArduinoStreamUtils/extras/test/ReadLoggingClientTest.cpp b/lib/ArduinoStreamUtils/extras/test/ReadLoggingClientTest.cpp new file mode 100644 index 00000000..bbd0128a --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/ReadLoggingClientTest.cpp @@ -0,0 +1,139 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#include "FailingAllocator.hpp" + +#include "StreamUtils/Clients/MemoryClient.hpp" +#include "StreamUtils/Clients/ReadLoggingClient.hpp" +#include "StreamUtils/Clients/SpyingClient.hpp" +#include "StreamUtils/Prints/StringPrint.hpp" + +#include "doctest.h" + +using namespace StreamUtils; + +TEST_CASE("ReadLoggingClient") { + MemoryClient target(4); + + StringPrint log; + SpyingClient upstreamSpy{target, log}; + + StringPrint output; + ReadLoggingClient loggingClient{upstreamSpy, output}; + + SUBCASE("available()") { + target.print("ABC"); + + size_t n = loggingClient.available(); + + CHECK(n == 3); + CHECK(log.str() == "available() -> 3"); + CHECK(output.str() == ""); + } + + SUBCASE("connect(IPAddress)") { + int n = loggingClient.connect(IPAddress("1.2.3.4"), 80); + + CHECK(n == 1); + CHECK(log.str() == "connect('1.2.3.4', 80) -> 1"); + CHECK(output.str() == ""); + } + + SUBCASE("connect(const char*)") { + int n = loggingClient.connect("1.2.3.4", 80); + + CHECK(n == 1); + CHECK(log.str() == "connect('1.2.3.4', 80) -> 1"); + CHECK(output.str() == ""); + } + + SUBCASE("connected()") { + uint8_t n = loggingClient.connected(); + + CHECK(n == false); + CHECK(log.str() == "connected() -> 0"); + CHECK(output.str() == ""); + } + + SUBCASE("stop()") { + loggingClient.stop(); + + CHECK(log.str() == "stop()"); + CHECK(output.str() == ""); + } + + SUBCASE("operator bool()") { + bool n = loggingClient.operator bool(); + + CHECK(n == true); + CHECK(log.str() == "operator bool() -> true"); + CHECK(output.str() == ""); + } + + SUBCASE("peek()") { + target.print("ABC"); + + int n = loggingClient.peek(); + + CHECK(n == 'A'); + CHECK(log.str() == "peek() -> 65"); + CHECK(output.str() == ""); + } + + SUBCASE("read()") { + target.print("ABC"); + + int n = loggingClient.read(); + + CHECK(n == 'A'); + CHECK(log.str() == "read() -> 65"); + CHECK(output.str() == "A"); + } + + SUBCASE("read(uint8_t*,size_t)") { + target.print("ABC"); + + uint8_t s[4] = {0}; + size_t n = loggingClient.read(s, 4); + + CHECK(n == 3); + CHECK(log.str() == "read(4) -> 3 [timeout]"); + CHECK(output.str() == "ABC"); + } + + SUBCASE("readBytes()") { + target.print("ABC"); + + char s[4] = {0}; + size_t n = loggingClient.readBytes(s, 4); + + CHECK(n == 3); + CHECK(output.str() == "ABC"); +#if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL + CHECK(log.str() == "readBytes(4) -> 3 [timeout]"); +#endif + } + + SUBCASE("write(char)") { + int n = loggingClient.write('A'); + + CHECK(n == 1); + CHECK(log.str() == "write('A') -> 1"); + CHECK(output.str() == ""); + } + + SUBCASE("write(char*,size_t)") { + int n = loggingClient.write("ABCDEF", 6); + + CHECK(n == 4); + CHECK(log.str() == "write('ABCDEF', 6) -> 4"); + CHECK(output.str() == ""); + } + + SUBCASE("flush()") { + loggingClient.flush(); + + CHECK(log.str() == "flush()"); + } +} diff --git a/lib/ArduinoStreamUtils/extras/test/ReadLoggingStreamTest.cpp b/lib/ArduinoStreamUtils/extras/test/ReadLoggingStreamTest.cpp new file mode 100644 index 00000000..aa2c5744 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/ReadLoggingStreamTest.cpp @@ -0,0 +1,89 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#include "FailingAllocator.hpp" + +#include "StreamUtils/Prints/StringPrint.hpp" +#include "StreamUtils/Streams/MemoryStream.hpp" +#include "StreamUtils/Streams/ReadLoggingStream.hpp" +#include "StreamUtils/Streams/SpyingStream.hpp" + +#include "doctest.h" + +using namespace StreamUtils; + +TEST_CASE("ReadLoggingStream") { + MemoryStream upstream(4); + + StringPrint log; + SpyingStream upstreamSpy{upstream, log}; + + StringPrint output; + ReadLoggingStream loggingStream{upstreamSpy, output}; + + SUBCASE("available()") { + upstream.print("ABC"); + + size_t n = loggingStream.available(); + + CHECK(n == 3); + CHECK(log.str() == "available() -> 3"); + CHECK(output.str() == ""); + } + + SUBCASE("peek()") { + upstream.print("ABC"); + + int n = loggingStream.peek(); + + CHECK(n == 'A'); + CHECK(log.str() == "peek() -> 65"); + CHECK(output.str() == ""); + } + + SUBCASE("read()") { + upstream.print("ABC"); + + int n = loggingStream.read(); + + CHECK(n == 'A'); + CHECK(log.str() == "read() -> 65"); + CHECK(output.str() == "A"); + } + + SUBCASE("readBytes()") { + upstream.print("ABC"); + + char s[4] = {0}; + size_t n = loggingStream.readBytes(s, 4); + + CHECK(n == 3); + CHECK(output.str() == "ABC"); +#if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL + CHECK(log.str() == "readBytes(4) -> 3 [timeout]"); +#endif + } + + SUBCASE("write(char)") { + int n = loggingStream.write('A'); + + CHECK(n == 1); + CHECK(log.str() == "write('A') -> 1"); + CHECK(output.str() == ""); + } + + SUBCASE("write(char*,size_t)") { + int n = loggingStream.write("ABCDEF", 6); + + CHECK(n == 4); + CHECK(log.str() == "write('ABCDEF', 6) -> 4"); + CHECK(output.str() == ""); + } + + SUBCASE("flush()") { + loggingStream.flush(); + + CHECK(log.str() == "flush()"); + } +} diff --git a/lib/ArduinoStreamUtils/extras/test/ReadThrottlingStreamTest.cpp b/lib/ArduinoStreamUtils/extras/test/ReadThrottlingStreamTest.cpp new file mode 100644 index 00000000..bb87dc59 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/ReadThrottlingStreamTest.cpp @@ -0,0 +1,89 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#include "FailingAllocator.hpp" + +#include "StreamUtils/Streams/ReadThrottlingStream.hpp" +#include "StreamUtils/Streams/StringStream.hpp" + +#include "doctest.h" + +#include + +using namespace StreamUtils; + +class SpyingThrottler { + public: + SpyingThrottler(uint32_t rate) { + _log << "C(" << rate << ")"; + } + + SpyingThrottler(const SpyingThrottler& src) { + _log << src._log.str(); + } + + void throttle() { + _log << "T"; + } + + std::string log() const { + return _log.str(); + } + + private: + std::stringstream _log; +}; + +using ReadThrottlingStream = BasicReadThrottlingStream; + +TEST_CASE("ReadThrottlingStream") { + StringStream upstream; + + ReadThrottlingStream throttledStream{upstream, 4}; + Stream& stream = throttledStream; + const SpyingThrottler& throttler = throttledStream.throttler(); + + SUBCASE("available()") { + upstream.print("ABCD"); + CHECK(stream.available() == 4); + CHECK(throttler.log() == "C(4)"); + } + + SUBCASE("read()") { + upstream.print("ABCD"); + int n = stream.read(); + + CHECK(n == 'A'); + CHECK(throttler.log() == "C(4)T"); + } + + SUBCASE("readBytes()") { + upstream.print("ABCD"); + char output[8] = {0}; + + SUBCASE("read more than available") { + size_t n = stream.readBytes(output, sizeof(output)); + + CHECK(n == 4); + CHECK(strcmp("ABCD", output) == 0); + CHECK(throttler.log() == "C(4)TTTTT"); + } + + SUBCASE("read less than available") { + size_t n = stream.readBytes(output, 2); + + CHECK(n == 2); + CHECK(strcmp("AB", output) == 0); + CHECK(throttler.log() == "C(4)TT"); + } + + SUBCASE("read as many as available") { + size_t n = stream.readBytes(output, 4); + + CHECK(n == 4); + CHECK(strcmp("ABCD", output) == 0); + CHECK(throttler.log() == "C(4)TTTT"); + } + } +} diff --git a/lib/ArduinoStreamUtils/extras/test/SpyingAllocator.hpp b/lib/ArduinoStreamUtils/extras/test/SpyingAllocator.hpp new file mode 100644 index 00000000..89f5c087 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/SpyingAllocator.hpp @@ -0,0 +1,36 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include + +#include "StreamUtils/Ports/DefaultAllocator.hpp" + +class SpyingAllocator { + public: + SpyingAllocator(Print& log) : _log(&log) {} + + bool forceFail = false; + + void* allocate(size_t n) { + void* ptr = forceFail ? 0 : _allocator.allocate(n); + _log->print("allocate("); + _log->print(n); + _log->print(") -> "); + _log->print(ptr ? "ptr" : "null"); + return ptr; + } + + void deallocate(void* ptr) { + _log->print("deallocate("); + _log->print(ptr ? "ptr" : "null"); + _log->print(")"); + _allocator.deallocate(ptr); + } + + private: + Print* _log; + StreamUtils::DefaultAllocator _allocator; +}; diff --git a/lib/ArduinoStreamUtils/extras/test/StringPrintTest.cpp b/lib/ArduinoStreamUtils/extras/test/StringPrintTest.cpp new file mode 100644 index 00000000..24163bf7 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/StringPrintTest.cpp @@ -0,0 +1,77 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#include "StreamUtils/Prints/StringPrint.hpp" + +#include "doctest.h" + +using namespace StreamUtils; + +TEST_CASE("StringPrint") { + WHEN("Constructed with no argument") { + StringPrint s; + + THEN("str() return an empty String") { + CHECK(s.str() == ""); + } + + THEN("write(uint8_t) appends the string") { + s.write('A'); + s.write('B'); + CHECK(s.str() == "AB"); + } + + THEN("write(uint8_t) return 1") { + CHECK(s.write('A') == 1); + CHECK(s.write('B') == 1); + } + + THEN("write(0) return 0") { + CHECK(s.write(0) == 0); + } + + THEN("write(uint8_t*, size_t) appends the string") { + s.write(reinterpret_cast("ABXXX"), 2); + s.write(reinterpret_cast("CDEXX"), 3); + CHECK(s.str() == "ABCDE"); + } + + THEN("write(uint8_t*, size_t) return number of chars written") { + uint8_t dummy[32] = {1, 2, 3, 0, 5, 6}; + CHECK(s.write(dummy, 2) == 2); + CHECK(s.write(dummy, 3) == 3); + CHECK(s.write(dummy, 4) == 3); + } + + THEN("str(String) sets the string") { + s.str("world!"); + CHECK(s.str() == "world!"); + } + } + + WHEN("Constructed with string") { + StringPrint s("hello"); + + THEN("str() return the stirng passed to contructor") { + CHECK(s.str() == "hello"); + } + + THEN("write(uint8_t) appends the string") { + s.write('A'); + s.write('B'); + CHECK(s.str() == "helloAB"); + } + + THEN("write(uint8_t*, size_t) appends the string") { + s.write(reinterpret_cast("ABXXX"), 2); + s.write(reinterpret_cast("CDEXX"), 3); + CHECK(s.str() == "helloABCDE"); + } + + THEN("str(String) replaces the string") { + s.str("world!"); + CHECK(s.str() == "world!"); + } + } +} diff --git a/lib/ArduinoStreamUtils/extras/test/StringStreamTest.cpp b/lib/ArduinoStreamUtils/extras/test/StringStreamTest.cpp new file mode 100644 index 00000000..cf0821c8 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/StringStreamTest.cpp @@ -0,0 +1,116 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#include "StreamUtils/Streams/StringStream.hpp" + +#include "doctest.h" + +using namespace StreamUtils; + +TEST_CASE("StringStream") { + WHEN("Constructed with no argument") { + StringStream s; + + THEN("str() return an empty String") { + CHECK(s.str() == ""); + } + + THEN("write(uint8_t) appends the string") { + s.write('A'); + s.write('B'); + CHECK(s.str() == "AB"); + } + + THEN("write(uint8_t) return 1") { + CHECK(s.write('A') == 1); + CHECK(s.write('B') == 1); + } + + THEN("write(0) return 0") { + CHECK(s.write(0) == 0); + } + + THEN("write(uint8_t*, size_t) appends the string") { + s.write(reinterpret_cast("ABXXX"), 2); + s.write(reinterpret_cast("CDEXX"), 3); + CHECK(s.str() == "ABCDE"); + } + + THEN("write(uint8_t*, size_t) return number of chars written") { + uint8_t dummy[32] = {1, 2, 3, 0, 5, 6}; + CHECK(s.write(dummy, 2) == 2); + CHECK(s.write(dummy, 3) == 3); + CHECK(s.write(dummy, 4) == 3); + } + + THEN("str(String) sets the string") { + s.str("world!"); + CHECK(s.str() == "world!"); + } + + THEN("available() returns 0") { + CHECK(s.available() == 0); + } + + THEN("peek() return -1") { + CHECK(s.peek() == -1); + } + + THEN("peek() return -1") { + CHECK(s.read() == -1); + } + } + + WHEN("Constructed with string") { + StringStream s("hello"); + + THEN("str() returns the string passed to contructor") { + CHECK(s.str() == "hello"); + } + + THEN("available() returns the length passed to the constructor") { + CHECK(s.available() == 5); + } + + THEN("write(uint8_t) appends the string") { + s.write('A'); + s.write('B'); + CHECK(s.str() == "helloAB"); + } + + THEN("write(uint8_t*, size_t) appends the string") { + s.write(reinterpret_cast("ABXXX"), 2); + s.write(reinterpret_cast("CDEXX"), 3); + CHECK(s.str() == "helloABCDE"); + } + + THEN("str(String) replaces the string") { + s.str("world!"); + CHECK(s.str() == "world!"); + } + + THEN("peek() return the first char") { + CHECK(s.peek() == 'h'); + } + + THEN("peek() return the next char") { + CHECK(s.read() == 'h'); + CHECK(s.read() == 'e'); + } + + THEN("readBytes() can return the begining of the string") { + char buffer[32] = {0}; + CHECK(s.readBytes(buffer, 3) == 3); + CHECK(buffer == std::string("hel")); + CHECK(s.available() == 2); + } + + THEN("readBytes() can return the whole string") { + char buffer[32] = {0}; + CHECK(s.readBytes(buffer, 32) == 5); + CHECK(buffer == std::string("hello")); + CHECK(s.available() == 0); + } + } +} diff --git a/lib/ArduinoStreamUtils/extras/test/WaitingPrintTest.cpp b/lib/ArduinoStreamUtils/extras/test/WaitingPrintTest.cpp new file mode 100644 index 00000000..8a691204 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/WaitingPrintTest.cpp @@ -0,0 +1,104 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#include "FailingAllocator.hpp" + +#include "StreamUtils/Prints/StringPrint.hpp" +#include "StreamUtils/Prints/WaitingPrint.hpp" +#include "StreamUtils/Streams/MemoryStream.hpp" +#include "StreamUtils/Streams/SpyingStream.hpp" + +#include "doctest.h" + +using namespace StreamUtils; + +TEST_CASE("WaitingPrint") { + MemoryStream upstream(4); + + StringPrint log; + SpyingStream spy{upstream, log}; + WaitingPrint stream{static_cast(spy), [&spy]() { spy.flush(); }}; + + SUBCASE("write(char*, size_t)") { + SUBCASE("no need to wait") { + CHECK(stream.print("ABC") == 3); + + CHECK(log.str() == "write('ABC', 3) -> 3"); + } + + SUBCASE("need to wait") { + CHECK(stream.print("ABCDEFG") == 7); + + CHECK(log.str() == + "write('ABCDEFG', 7) -> 4" + "flush()" + "write('EFG', 3) -> 3"); + } + + SUBCASE("need to wait twice") { + CHECK(stream.print("ABCDEFGIJKL") == 11); + + CHECK(log.str() == + "write('ABCDEFGIJKL', 11) -> 4" + "flush()" + "write('EFGIJKL', 7) -> 4" + "flush()" + "write('JKL', 3) -> 3"); + } + + SUBCASE("doesn't wait when timeout is 0") { + stream.setTimeout(0); + + CHECK(stream.print("ABCDEFG") == 4); + + CHECK(log.str() == "write('ABCDEFG', 7) -> 4"); + } + } + + SUBCASE("write(char)") { + SUBCASE("no need to wait") { + CHECK(stream.write('A') == 1); + CHECK(stream.write('B') == 1); + CHECK(stream.write('C') == 1); + + CHECK(log.str() == + "write('A') -> 1" + "write('B') -> 1" + "write('C') -> 1"); + } + + SUBCASE("need to wait") { + for (int i = 0; i < 7; i++) + CHECK(stream.write("ABCDEFG"[i]) == 1); + + CHECK(log.str() == + "write('A') -> 1" + "write('B') -> 1" + "write('C') -> 1" + "write('D') -> 1" + "write('E') -> 0" + "flush()" + "write('E') -> 1" + "write('F') -> 1" + "write('G') -> 1"); + } + + SUBCASE("doesn't wait when timeout is 0") { + stream.setTimeout(0); + + CHECK(stream.write('A') == 1); + CHECK(stream.write('B') == 1); + CHECK(stream.write('C') == 1); + CHECK(stream.write('D') == 1); + CHECK(stream.write('E') == 0); + + CHECK(log.str() == + "write('A') -> 1" + "write('B') -> 1" + "write('C') -> 1" + "write('D') -> 1" + "write('E') -> 0"); + } + } +} diff --git a/lib/ArduinoStreamUtils/extras/test/WriteBufferingClientTest.cpp b/lib/ArduinoStreamUtils/extras/test/WriteBufferingClientTest.cpp new file mode 100644 index 00000000..e300a06f --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/WriteBufferingClientTest.cpp @@ -0,0 +1,203 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#include "FailingAllocator.hpp" + +#include "StreamUtils/Clients/MemoryClient.hpp" +#include "StreamUtils/Clients/SpyingClient.hpp" +#include "StreamUtils/Clients/WriteBufferingClient.hpp" +#include "StreamUtils/Prints/StringPrint.hpp" + +#include "doctest.h" + +using namespace StreamUtils; + +TEST_CASE("WriteBufferingClient") { + MemoryClient target(64); + + StringPrint log; + SpyingClient spy{target, log}; + + GIVEN("capacity is 4") { + WriteBufferingClient bufferingClient{spy, 4}; + + SUBCASE("available()") { + target.print("ABC"); + + CHECK(bufferingClient.available() == 3); + CHECK(log.str() == "available() -> 3"); + } + + SUBCASE("connect(IPAddress)") { + int n = bufferingClient.connect(IPAddress("1.2.3.4"), 80); + + CHECK(n == 1); + CHECK(log.str() == "connect('1.2.3.4', 80) -> 1"); + } + + SUBCASE("connect(const char*)") { + int n = bufferingClient.connect("1.2.3.4", 80); + + CHECK(n == 1); + CHECK(log.str() == "connect('1.2.3.4', 80) -> 1"); + } + + SUBCASE("connected()") { + uint8_t n = bufferingClient.connected(); + + CHECK(n == false); + CHECK(log.str() == "connected() -> 0"); + } + + SUBCASE("stop()") { + bufferingClient.write("ABC", 3); + bufferingClient.stop(); + + CHECK(log.str() == + "write('ABC', 3) -> 3" + "stop()"); + } + + SUBCASE("operator bool()") { + bool n = bufferingClient.operator bool(); + + CHECK(n == true); + CHECK(log.str() == "operator bool() -> true"); + } + + SUBCASE("flush() forwards to target)") { + bufferingClient.flush(); + + CHECK(log.str() == "flush()"); + } + + SUBCASE("flush() calls write() and flush()") { + bufferingClient.write("ABC", 3); + bufferingClient.flush(); + + CHECK(log.str() == + "write('ABC', 3) -> 3" + "flush()"); + } + + SUBCASE("peek()") { + target.print("ABC"); + + CHECK(bufferingClient.peek() == 'A'); + CHECK(log.str() == "peek() -> 65"); + } + + SUBCASE("read()") { + target.print("ABC"); + + CHECK(bufferingClient.read() == 'A'); + CHECK(log.str() == "read() -> 65"); + } + + SUBCASE("readBytes()") { + target.print("ABC"); + + char s[4] = {0}; + int n = bufferingClient.readBytes(s, 3); + + CHECK(n == 3); + CHECK(s == std::string("ABC")); +#if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL + CHECK(log.str() == "readBytes(3) -> 3"); +#endif + } + + GIVEN("the buffer is empty") { + SUBCASE("write(uint8_t)") { + int n = bufferingClient.write('A'); + + CHECK(n == 1); + CHECK(log.str() == ""); + } + + SUBCASE("write(uint8_t) should flush") { + bufferingClient.write('A'); + bufferingClient.write('B'); + bufferingClient.write('C'); + bufferingClient.write('D'); + bufferingClient.write('E'); + + CHECK(log.str() == "write('ABCD', 4) -> 4"); + } + + SUBCASE("write(char*,3) goes in buffer") { + size_t n = bufferingClient.write("ABC", 3); + + CHECK(n == 3); + CHECK(log.str() == ""); + } + + SUBCASE("write(char*,4) bypasses buffer") { + size_t n = bufferingClient.write("ABCD", 4); + + CHECK(n == 4); + CHECK(log.str() == "write('ABCD', 4) -> 4"); + } + SUBCASE("write(char*,2) bypasses buffer") { + size_t n = bufferingClient.write("ABCD", 4); + + CHECK(n == 4); + CHECK(log.str() == "write('ABCD', 4) -> 4"); + } + } + + GIVEN("one byte in the buffer") { + bufferingClient.write('A'); + + SUBCASE("write(char*,3) goes in buffer and flush") { + size_t n = bufferingClient.write("BCD", 3); + + CHECK(n == 3); + CHECK(log.str() == "write('ABCD', 4) -> 4"); + } + + SUBCASE("write(char*,7) bypasses") { + size_t n = bufferingClient.write("BCDEFGH", 7); + + CHECK(n == 7); + CHECK(log.str() == + "write('ABCD', 4) -> 4" + "write('EFGH', 4) -> 4"); + } + } + } + + GIVEN("capacity is 0") { + WriteBufferingClient bufferingClient{spy, 0}; + + SUBCASE("write(uint8_t) forwards to target") { + int n = bufferingClient.write('X'); + + CHECK(n == 1); + CHECK(log.str() == "write('X') -> 1"); + } + + SUBCASE("write(char*,1) forwards to target") { + int n = bufferingClient.write("A", 1); + + CHECK(n == 1); + CHECK(log.str() == "write('A', 1) -> 1"); + } + + SUBCASE("flush() forwards to target") { + bufferingClient.flush(); + + CHECK(log.str() == "flush()"); + } + } + + SUBCASE("Destructor should flush") { + { + WriteBufferingClient bufferingClient{spy, 10}; + bufferingClient.write("ABC", 3); + } + + CHECK(log.str() == "write('ABC', 3) -> 3"); + } +} diff --git a/lib/ArduinoStreamUtils/extras/test/WriteBufferingStreamTest.cpp b/lib/ArduinoStreamUtils/extras/test/WriteBufferingStreamTest.cpp new file mode 100644 index 00000000..0dae701e --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/WriteBufferingStreamTest.cpp @@ -0,0 +1,166 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#include "FailingAllocator.hpp" + +#include "StreamUtils/Prints/StringPrint.hpp" +#include "StreamUtils/Streams/MemoryStream.hpp" +#include "StreamUtils/Streams/SpyingStream.hpp" +#include "StreamUtils/Streams/WriteBufferingStream.hpp" + +#include "doctest.h" + +using namespace StreamUtils; + +TEST_CASE("WriteBufferingStream") { + MemoryStream upstream(64); + + StringPrint log; + SpyingStream spy{upstream, log}; + + GIVEN("capacity is 4") { + WriteBufferingStream stream{spy, 4}; + + SUBCASE("available()") { + upstream.print("ABC"); + + CHECK(stream.available() == 3); + CHECK(log.str() == "available() -> 3"); + } + + SUBCASE("flush() forwards to upstream)") { + stream.flush(); + + CHECK(log.str() == "flush()"); + } + + SUBCASE("flush() calls write() and flush()") { + stream.write("ABC", 3); + stream.flush(); + + CHECK(log.str() == + "write('ABC', 3) -> 3" + "flush()"); + } + + SUBCASE("peek()") { + upstream.print("ABC"); + + CHECK(stream.peek() == 'A'); + CHECK(log.str() == "peek() -> 65"); + } + + SUBCASE("read()") { + upstream.print("ABC"); + + CHECK(stream.read() == 'A'); + CHECK(log.str() == "read() -> 65"); + } + + SUBCASE("readBytes()") { + upstream.print("ABC"); + + char s[4] = {0}; + int n = stream.readBytes(s, 3); + + CHECK(n == 3); + CHECK(s == std::string("ABC")); +#if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL + CHECK(log.str() == "readBytes(3) -> 3"); +#endif + } + + GIVEN("the buffer is empty") { + SUBCASE("write(uint8_t)") { + int n = stream.write('A'); + + CHECK(n == 1); + CHECK(log.str() == ""); + } + + SUBCASE("write(uint8_t) should flush") { + stream.write('A'); + stream.write('B'); + stream.write('C'); + stream.write('D'); + stream.write('E'); + + CHECK(log.str() == "write('ABCD', 4) -> 4"); + } + + SUBCASE("write(char*,3) goes in buffer") { + size_t n = stream.write("ABC", 3); + + CHECK(n == 3); + CHECK(log.str() == ""); + } + + SUBCASE("write(char*,4) bypasses buffer") { + size_t n = stream.write("ABCD", 4); + + CHECK(n == 4); + CHECK(log.str() == "write('ABCD', 4) -> 4"); + } + SUBCASE("write(char*,2) bypasses buffer") { + size_t n = stream.write("ABCD", 4); + + CHECK(n == 4); + CHECK(log.str() == "write('ABCD', 4) -> 4"); + } + } + + GIVEN("one byte in the buffer") { + stream.write('A'); + + SUBCASE("write(char*,3) goes in buffer and flush") { + size_t n = stream.write("BCD", 3); + + CHECK(n == 3); + CHECK(log.str() == "write('ABCD', 4) -> 4"); + } + + SUBCASE("write(char*,7) bypasses") { + size_t n = stream.write("BCDEFGH", 7); + + CHECK(n == 7); + CHECK(log.str() == + "write('ABCD', 4) -> 4" + "write('EFGH', 4) -> 4"); + } + } + } + + GIVEN("capacity is 0") { + WriteBufferingStream stream{spy, 0}; + + SUBCASE("write(uint8_t) forwards to upstream") { + int n = stream.write('X'); + + CHECK(n == 1); + CHECK(log.str() == "write('X') -> 1"); + } + + SUBCASE("write(char*,1) forwards to upstream") { + int n = stream.write("A", 1); + + CHECK(n == 1); + CHECK(log.str() == "write('A', 1) -> 1"); + } + + SUBCASE("flush() forwards to upstream") { + stream.flush(); + + CHECK(log.str() == "flush()"); + } + } + + SUBCASE("Destructor should flush") { + { + WriteBufferingStream stream{spy, 10}; + stream.write("ABC", 3); + } + + CHECK(log.str() == "write('ABC', 3) -> 3"); + } +} diff --git a/lib/ArduinoStreamUtils/extras/test/WriteLoggingClientTest.cpp b/lib/ArduinoStreamUtils/extras/test/WriteLoggingClientTest.cpp new file mode 100644 index 00000000..3d5c6115 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/WriteLoggingClientTest.cpp @@ -0,0 +1,140 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#include "FailingAllocator.hpp" + +#include "StreamUtils/Clients/MemoryClient.hpp" +#include "StreamUtils/Clients/SpyingClient.hpp" +#include "StreamUtils/Clients/WriteLoggingClient.hpp" +#include "StreamUtils/Prints/StringPrint.hpp" + +#include "doctest.h" + +using namespace StreamUtils; + +TEST_CASE("WriteLoggingClient") { + MemoryClient target(4); + + StringPrint log; + SpyingClient spy{target, log}; + + StringPrint output; + WriteLoggingClient loggingClient{spy, output}; + + SUBCASE("available()") { + target.print("ABC"); + + size_t n = loggingClient.available(); + + CHECK(n == 3); + CHECK(log.str() == "available() -> 3"); + CHECK(output.str() == ""); + } + + SUBCASE("connect(IPAddress)") { + int n = loggingClient.connect(IPAddress("1.2.3.4"), 80); + + CHECK(n == 1); + CHECK(log.str() == "connect('1.2.3.4', 80) -> 1"); + CHECK(output.str() == ""); + } + + SUBCASE("connect(const char*)") { + int n = loggingClient.connect("1.2.3.4", 80); + + CHECK(n == 1); + CHECK(log.str() == "connect('1.2.3.4', 80) -> 1"); + CHECK(output.str() == ""); + } + + SUBCASE("connected()") { + uint8_t n = loggingClient.connected(); + + CHECK(n == false); + CHECK(log.str() == "connected() -> 0"); + CHECK(output.str() == ""); + } + + SUBCASE("stop()") { + loggingClient.stop(); + + CHECK(log.str() == "stop()"); + CHECK(output.str() == ""); + } + + SUBCASE("operator bool()") { + bool n = loggingClient.operator bool(); + + CHECK(n == true); + CHECK(log.str() == "operator bool() -> true"); + CHECK(output.str() == ""); + } + + SUBCASE("peek()") { + target.print("ABC"); + + int n = loggingClient.peek(); + + CHECK(n == 'A'); + CHECK(log.str() == "peek() -> 65"); + CHECK(output.str() == ""); + } + + SUBCASE("read()") { + target.print("ABC"); + + int n = loggingClient.read(); + + CHECK(n == 'A'); + CHECK(log.str() == "read() -> 65"); + CHECK(output.str() == ""); + } + + SUBCASE("read(uint8_t*,size_t)") { + target.print("ABC"); + + uint8_t s[4] = {0}; + size_t n = loggingClient.read(s, 4); + + CHECK(n == 3); + CHECK(log.str() == "read(4) -> 3 [timeout]"); + CHECK(output.str() == ""); + } + + SUBCASE("readBytes()") { + target.print("ABC"); + + char s[4] = {0}; + size_t n = loggingClient.readBytes(s, 4); + + CHECK(n == 3); + CHECK(output.str() == ""); +#if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL + CHECK(log.str() == "readBytes(4) -> 3 [timeout]"); +#endif + } + + SUBCASE("write(char)") { + int n = loggingClient.write('A'); + + CHECK(n == 1); + CHECK(log.str() == "write('A') -> 1"); + CHECK(output.str() == "A"); + } + + SUBCASE("write(char*,size_t)") { + int n = loggingClient.write("ABCDEF", 6); + + CHECK(n == 4); + CHECK(log.str() == "write('ABCDEF', 6) -> 4"); + CHECK(output.str() == "ABCD"); + } + + SUBCASE("flush()") { + loggingClient.flush(); + + CHECK(output.str() == ""); + CHECK(log.str() == "flush()"); + } +} diff --git a/lib/ArduinoStreamUtils/extras/test/WriteLoggingStreamTest.cpp b/lib/ArduinoStreamUtils/extras/test/WriteLoggingStreamTest.cpp new file mode 100644 index 00000000..e38ae525 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/WriteLoggingStreamTest.cpp @@ -0,0 +1,90 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#include "FailingAllocator.hpp" + +#include "StreamUtils/Prints/StringPrint.hpp" +#include "StreamUtils/Streams/MemoryStream.hpp" +#include "StreamUtils/Streams/SpyingStream.hpp" +#include "StreamUtils/Streams/WriteLoggingStream.hpp" + +#include "doctest.h" + +using namespace StreamUtils; + +TEST_CASE("WriteLoggingStream") { + MemoryStream upstream(4); + + StringPrint log; + SpyingStream upstreamSpy{upstream, log}; + + StringPrint output; + WriteLoggingStream loggingStream{upstreamSpy, output}; + + SUBCASE("available()") { + upstream.print("ABC"); + + size_t n = loggingStream.available(); + + CHECK(n == 3); + CHECK(log.str() == "available() -> 3"); + CHECK(output.str() == ""); + } + + SUBCASE("peek()") { + upstream.print("ABC"); + + int n = loggingStream.peek(); + + CHECK(n == 'A'); + CHECK(log.str() == "peek() -> 65"); + CHECK(output.str() == ""); + } + + SUBCASE("read()") { + upstream.print("ABC"); + + int n = loggingStream.read(); + + CHECK(n == 'A'); + CHECK(log.str() == "read() -> 65"); + CHECK(output.str() == ""); + } + + SUBCASE("readBytes()") { + upstream.print("ABC"); + + char s[4] = {0}; + size_t n = loggingStream.readBytes(s, 4); + + CHECK(n == 3); + CHECK(output.str() == ""); +#if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL + CHECK(log.str() == "readBytes(4) -> 3 [timeout]"); +#endif + } + + SUBCASE("write(char)") { + int n = loggingStream.write('A'); + + CHECK(n == 1); + CHECK(log.str() == "write('A') -> 1"); + CHECK(output.str() == "A"); + } + + SUBCASE("write(char*,size_t)") { + int n = loggingStream.write("ABCDEF", 6); + + CHECK(n == 4); + CHECK(log.str() == "write('ABCDEF', 6) -> 4"); + CHECK(output.str() == "ABCD"); + } + + SUBCASE("flush()") { + loggingStream.flush(); + + CHECK(log.str() == "flush()"); + CHECK(output.str() == ""); + } +} diff --git a/lib/ArduinoStreamUtils/extras/test/WriteWaitingClientTest.cpp b/lib/ArduinoStreamUtils/extras/test/WriteWaitingClientTest.cpp new file mode 100644 index 00000000..20b2d901 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/WriteWaitingClientTest.cpp @@ -0,0 +1,104 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#include "FailingAllocator.hpp" + +#include "StreamUtils/Clients/MemoryClient.hpp" +#include "StreamUtils/Clients/SpyingClient.hpp" +#include "StreamUtils/Clients/WriteWaitingClient.hpp" +#include "StreamUtils/Prints/StringPrint.hpp" + +#include "doctest.h" + +using namespace StreamUtils; + +TEST_CASE("WriteWaitingClient") { + MemoryClient upstream(4); + + StringPrint log; + SpyingClient spy{upstream, log}; + WriteWaitingClient client{spy, [&spy]() { spy.flush(); }}; + + SUBCASE("write(char*, size_t)") { + SUBCASE("no need to wait") { + CHECK(client.print("ABC") == 3); + + CHECK(log.str() == "write('ABC', 3) -> 3"); + } + + SUBCASE("need to wait") { + CHECK(client.print("ABCDEFG") == 7); + + CHECK(log.str() == + "write('ABCDEFG', 7) -> 4" + "flush()" + "write('EFG', 3) -> 3"); + } + + SUBCASE("need to wait twice") { + CHECK(client.print("ABCDEFGIJKL") == 11); + + CHECK(log.str() == + "write('ABCDEFGIJKL', 11) -> 4" + "flush()" + "write('EFGIJKL', 7) -> 4" + "flush()" + "write('JKL', 3) -> 3"); + } + + SUBCASE("doesn't wait when timeout is 0") { + client.setTimeout(0); + + CHECK(client.print("ABCDEFG") == 4); + + CHECK(log.str() == "write('ABCDEFG', 7) -> 4"); + } + } + + SUBCASE("write(char)") { + SUBCASE("no need to wait") { + CHECK(client.write('A') == 1); + CHECK(client.write('B') == 1); + CHECK(client.write('C') == 1); + + CHECK(log.str() == + "write('A') -> 1" + "write('B') -> 1" + "write('C') -> 1"); + } + + SUBCASE("need to wait") { + for (int i = 0; i < 7; i++) + CHECK(client.write("ABCDEFG"[i]) == 1); + + CHECK(log.str() == + "write('A') -> 1" + "write('B') -> 1" + "write('C') -> 1" + "write('D') -> 1" + "write('E') -> 0" + "flush()" + "write('E') -> 1" + "write('F') -> 1" + "write('G') -> 1"); + } + + SUBCASE("doesn't wait when timeout is 0") { + client.setTimeout(0); + + CHECK(client.write('A') == 1); + CHECK(client.write('B') == 1); + CHECK(client.write('C') == 1); + CHECK(client.write('D') == 1); + CHECK(client.write('E') == 0); + + CHECK(log.str() == + "write('A') -> 1" + "write('B') -> 1" + "write('C') -> 1" + "write('D') -> 1" + "write('E') -> 0"); + } + } +} diff --git a/lib/ArduinoStreamUtils/extras/test/WriteWaitingStreamTest.cpp b/lib/ArduinoStreamUtils/extras/test/WriteWaitingStreamTest.cpp new file mode 100644 index 00000000..f071ea02 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/WriteWaitingStreamTest.cpp @@ -0,0 +1,104 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#include "FailingAllocator.hpp" + +#include "StreamUtils/Prints/StringPrint.hpp" +#include "StreamUtils/Streams/MemoryStream.hpp" +#include "StreamUtils/Streams/SpyingStream.hpp" +#include "StreamUtils/Streams/WriteWaitingStream.hpp" + +#include "doctest.h" + +using namespace StreamUtils; + +TEST_CASE("WriteWaitingStream") { + MemoryStream upstream(4); + + StringPrint log; + SpyingStream spy{upstream, log}; + WriteWaitingStream stream{spy, [&spy]() { spy.flush(); }}; + + SUBCASE("write(char*, size_t)") { + SUBCASE("no need to wait") { + CHECK(stream.print("ABC") == 3); + + CHECK(log.str() == "write('ABC', 3) -> 3"); + } + + SUBCASE("need to wait") { + CHECK(stream.print("ABCDEFG") == 7); + + CHECK(log.str() == + "write('ABCDEFG', 7) -> 4" + "flush()" + "write('EFG', 3) -> 3"); + } + + SUBCASE("need to wait twice") { + CHECK(stream.print("ABCDEFGIJKL") == 11); + + CHECK(log.str() == + "write('ABCDEFGIJKL', 11) -> 4" + "flush()" + "write('EFGIJKL', 7) -> 4" + "flush()" + "write('JKL', 3) -> 3"); + } + + SUBCASE("doesn't wait when timeout is 0") { + stream.setTimeout(0); + + CHECK(stream.print("ABCDEFG") == 4); + + CHECK(log.str() == "write('ABCDEFG', 7) -> 4"); + } + } + + SUBCASE("write(char)") { + SUBCASE("no need to wait") { + CHECK(stream.write('A') == 1); + CHECK(stream.write('B') == 1); + CHECK(stream.write('C') == 1); + + CHECK(log.str() == + "write('A') -> 1" + "write('B') -> 1" + "write('C') -> 1"); + } + + SUBCASE("need to wait") { + for (int i = 0; i < 7; i++) + CHECK(stream.write("ABCDEFG"[i]) == 1); + + CHECK(log.str() == + "write('A') -> 1" + "write('B') -> 1" + "write('C') -> 1" + "write('D') -> 1" + "write('E') -> 0" + "flush()" + "write('E') -> 1" + "write('F') -> 1" + "write('G') -> 1"); + } + + SUBCASE("doesn't wait when timeout is 0") { + stream.setTimeout(0); + + CHECK(stream.write('A') == 1); + CHECK(stream.write('B') == 1); + CHECK(stream.write('C') == 1); + CHECK(stream.write('D') == 1); + CHECK(stream.write('E') == 0); + + CHECK(log.str() == + "write('A') -> 1" + "write('B') -> 1" + "write('C') -> 1" + "write('D') -> 1" + "write('E') -> 0"); + } + } +} diff --git a/lib/ArduinoStreamUtils/extras/test/cores/avr/Arduino.h b/lib/ArduinoStreamUtils/extras/test/cores/avr/Arduino.h new file mode 100644 index 00000000..e94f54ff --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/cores/avr/Arduino.h @@ -0,0 +1,18 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include +#include +#include +#include + +#include + +inline unsigned long millis() { + return static_cast(time(NULL)); +} + +inline void yield() {} \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/extras/test/cores/avr/CMakeLists.txt b/lib/ArduinoStreamUtils/extras/test/cores/avr/CMakeLists.txt new file mode 100644 index 00000000..4cc892e0 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/cores/avr/CMakeLists.txt @@ -0,0 +1,22 @@ +# StreamUtils - github.com/bblanchon/ArduinoStreamUtils +# Copyright Benoit Blanchon 2019-2021 +# MIT License + +add_library(AvrCore + Client.h + EEPROM.cpp + EEPROM.h + Print.h + Stream.h + WString.h +) + +target_include_directories(AvrCore + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} +) + +target_compile_definitions(AvrCore + PUBLIC + ARDUINO_ARCH_AVR +) diff --git a/lib/ArduinoStreamUtils/extras/test/cores/avr/Client.h b/lib/ArduinoStreamUtils/extras/test/cores/avr/Client.h new file mode 100644 index 00000000..79d7a5d1 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/cores/avr/Client.h @@ -0,0 +1,27 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include "Stream.h" +#include "WString.h" + +using IPAddress = String; + +struct Client : Stream { + virtual int connect(IPAddress ip, uint16_t port) = 0; + virtual int connect(const char *host, uint16_t port) = 0; + virtual uint8_t connected() = 0; + virtual void stop() = 0; + virtual operator bool() = 0; + + // Already in Stream + virtual size_t write(uint8_t) = 0; + virtual size_t write(const uint8_t *buf, size_t size) = 0; + virtual int available() = 0; + virtual int read() = 0; + + // Curiously not in Stream + virtual int read(uint8_t *buf, size_t size) = 0; +}; diff --git a/lib/ArduinoStreamUtils/extras/test/cores/avr/EEPROM.cpp b/lib/ArduinoStreamUtils/extras/test/cores/avr/EEPROM.cpp new file mode 100644 index 00000000..2b2eb1a8 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/cores/avr/EEPROM.cpp @@ -0,0 +1,12 @@ +#include "EEPROM.h" + +EEPROMClass EEPROM; +static uint8_t data[512]; + +uint8_t EEPROMClass::read(int address) { + return data[address]; +} + +void EEPROMClass::update(int address, uint8_t value) { + data[address] = value; +} diff --git a/lib/ArduinoStreamUtils/extras/test/cores/avr/EEPROM.h b/lib/ArduinoStreamUtils/extras/test/cores/avr/EEPROM.h new file mode 100644 index 00000000..b5e3360c --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/cores/avr/EEPROM.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +class EEPROMClass { + public: + uint8_t read(int); + void update(int, uint8_t); + // void write(int, uint8_t); <- it exists but we want to use update() instead +}; + +extern EEPROMClass EEPROM; diff --git a/lib/ArduinoStreamUtils/extras/test/cores/avr/Print.h b/lib/ArduinoStreamUtils/extras/test/cores/avr/Print.h new file mode 100644 index 00000000..34b48916 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/cores/avr/Print.h @@ -0,0 +1,42 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include +#include + +#include + +struct Print { + virtual size_t write(const uint8_t *buffer, size_t size) = 0; + virtual size_t write(uint8_t data) = 0; + virtual void flush() {} + + size_t write(const char *buffer, size_t size) { + return write((const uint8_t *)buffer, size); + } + + size_t print(const String &s) { + return write(s.c_str(), s.length()); + } + + size_t print(const char *s) { + return write(s, std::strlen(s)); + } + + size_t println() { + return 0; + } + + template + size_t print(const T &value) { + return print(String(value)); + } + + template + size_t println(const T &value) { + return print(value); + } +}; diff --git a/lib/ArduinoStreamUtils/extras/test/cores/avr/Stream.h b/lib/ArduinoStreamUtils/extras/test/cores/avr/Stream.h new file mode 100644 index 00000000..869b9d4c --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/cores/avr/Stream.h @@ -0,0 +1,41 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include "Print.h" + +struct Stream : Print { + virtual int available() = 0; + virtual int read() = 0; + virtual int peek() = 0; + + size_t readBytes(char *buffer, size_t length) { + size_t count = 0; + while (count < length) { + int c = timedRead(); + if (c < 0) + break; + *buffer++ = (char)c; + count++; + } + return count; + } + + String readString() { + String result; + int c; + while ((c = timedRead()) >= 0) { + result += static_cast(c); + } + return result; + } + + void setTimeout(unsigned long) {} + + protected: + int timedRead() { + return read(); + } +}; diff --git a/lib/ArduinoStreamUtils/extras/test/cores/avr/WString.h b/lib/ArduinoStreamUtils/extras/test/cores/avr/WString.h new file mode 100644 index 00000000..a4c911bd --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/cores/avr/WString.h @@ -0,0 +1,43 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include + +class String : private std::string { + public: + String() {} + String(const String& s) : std::string(s) {} + String(String&& s) : std::string(std::move(s)) {} + String(const char* s) : std::string(s) {} + String(int n) : std::string(std::to_string(n)) {} + + String& operator=(const String& rhs) { + std::string::operator=(rhs); + return *this; + } + + using std::string::c_str; + using std::string::length; + using std::string::operator+=; + using std::string::operator[]; + + void remove(unsigned int index, unsigned int count) { + erase(begin() + index, begin() + index + count); + } + + void toCharArray(char* buf, unsigned int bufsize, + unsigned int index = 0) const { + copy(buf, bufsize, index); + } + + friend bool operator==(const String& lhs, const char* rhs) { + return static_cast(lhs) == rhs; + } + + friend std::ostream& operator<<(std::ostream& lhs, const String& rhs) { + return lhs << static_cast(rhs); + } +}; diff --git a/lib/ArduinoStreamUtils/extras/test/cores/esp32/Arduino.h b/lib/ArduinoStreamUtils/extras/test/cores/esp32/Arduino.h new file mode 100644 index 00000000..e94f54ff --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/cores/esp32/Arduino.h @@ -0,0 +1,18 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include +#include +#include +#include + +#include + +inline unsigned long millis() { + return static_cast(time(NULL)); +} + +inline void yield() {} \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/extras/test/cores/esp32/CMakeLists.txt b/lib/ArduinoStreamUtils/extras/test/cores/esp32/CMakeLists.txt new file mode 100644 index 00000000..cc3cd733 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/cores/esp32/CMakeLists.txt @@ -0,0 +1,22 @@ +# StreamUtils - github.com/bblanchon/ArduinoStreamUtils +# Copyright Benoit Blanchon 2019-2021 +# MIT License + +add_library(Esp32Core + Client.h + EEPROM.cpp + EEPROM.h + Print.h + Stream.h + WString.h +) + +target_include_directories(Esp32Core + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} +) + +target_compile_definitions(Esp32Core + PUBLIC + ARDUINO_ARCH_ESP32 +) diff --git a/lib/ArduinoStreamUtils/extras/test/cores/esp32/Client.h b/lib/ArduinoStreamUtils/extras/test/cores/esp32/Client.h new file mode 100644 index 00000000..1fc8d8f0 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/cores/esp32/Client.h @@ -0,0 +1,26 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include "Stream.h" +#include "WString.h" + +using IPAddress = String; + +class Client : public Stream { + public: + virtual int connect(IPAddress ip, uint16_t port) = 0; + virtual int connect(const char *host, uint16_t port) = 0; + virtual size_t write(uint8_t) = 0; + virtual size_t write(const uint8_t *buf, size_t size) = 0; + virtual int available() = 0; + virtual int read() = 0; + virtual int read(uint8_t *buf, size_t size) = 0; + virtual int peek() = 0; + virtual void flush() = 0; + virtual void stop() = 0; + virtual uint8_t connected() = 0; + virtual operator bool() = 0; +}; diff --git a/lib/ArduinoStreamUtils/extras/test/cores/esp32/EEPROM.cpp b/lib/ArduinoStreamUtils/extras/test/cores/esp32/EEPROM.cpp new file mode 100644 index 00000000..a47dd834 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/cores/esp32/EEPROM.cpp @@ -0,0 +1,20 @@ +#include "EEPROM.h" + +#include // memcpy + +EEPROMClass EEPROM; +static uint8_t commitedData[512]; +static uint8_t pendingData[512]; + +uint8_t EEPROMClass::read(int address) { + return commitedData[address]; +} + +void EEPROMClass::write(int address, uint8_t value) { + pendingData[address] = value; +} + +bool EEPROMClass::commit() { + memcpy(commitedData, pendingData, 512); + return true; +} \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/extras/test/cores/esp32/EEPROM.h b/lib/ArduinoStreamUtils/extras/test/cores/esp32/EEPROM.h new file mode 100644 index 00000000..cd9eb00c --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/cores/esp32/EEPROM.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +class EEPROMClass { + public: + uint8_t read(int); + void write(int, uint8_t); + bool commit(); +}; + +extern EEPROMClass EEPROM; diff --git a/lib/ArduinoStreamUtils/extras/test/cores/esp32/Print.h b/lib/ArduinoStreamUtils/extras/test/cores/esp32/Print.h new file mode 100644 index 00000000..40ff3646 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/cores/esp32/Print.h @@ -0,0 +1,42 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include + +#include +#include + +struct Print { + virtual ~Print() {} + virtual size_t write(const uint8_t *buffer, size_t size) = 0; + virtual size_t write(uint8_t data) = 0; + + size_t write(const char *buffer, size_t size) { + return write((const uint8_t *)buffer, size); + } + + size_t print(const String &s) { + return write(s.c_str(), s.length()); + } + + size_t print(const char *s) { + return write(s, std::strlen(s)); + } + + size_t println() { + return 0; + } + + template + size_t print(const T &value) { + return print(String(value)); + } + + template + size_t println(const T &value) { + return print(value); + } +}; \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/extras/test/cores/esp32/Stream.h b/lib/ArduinoStreamUtils/extras/test/cores/esp32/Stream.h new file mode 100644 index 00000000..dae174d3 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/cores/esp32/Stream.h @@ -0,0 +1,48 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include "Print.h" + +struct Stream : Print { + virtual ~Stream() {} + + virtual int available() = 0; + virtual int read() = 0; + virtual int peek() = 0; + virtual void flush() = 0; + + virtual size_t readBytes(char *buffer, size_t length) { + size_t count = 0; + while (count < length) { + int c = timedRead(); + if (c < 0) + break; + *buffer++ = (char)c; + count++; + } + return count; + } + + virtual size_t readBytes(uint8_t *buffer, size_t length) { + return readBytes((char *)buffer, length); + } + + virtual String readString() { + String result; + int c; + while ((c = timedRead()) >= 0) { + result += c; + } + return result; + } + + void setTimeout(unsigned long) {} + + protected: + int timedRead() { + return read(); + } +}; diff --git a/lib/ArduinoStreamUtils/extras/test/cores/esp32/WString.h b/lib/ArduinoStreamUtils/extras/test/cores/esp32/WString.h new file mode 100644 index 00000000..9023e414 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/cores/esp32/WString.h @@ -0,0 +1,5 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#include "../avr/WString.h" \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/extras/test/cores/esp8266/Arduino.h b/lib/ArduinoStreamUtils/extras/test/cores/esp8266/Arduino.h new file mode 100644 index 00000000..e94f54ff --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/cores/esp8266/Arduino.h @@ -0,0 +1,18 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include +#include +#include +#include + +#include + +inline unsigned long millis() { + return static_cast(time(NULL)); +} + +inline void yield() {} \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/extras/test/cores/esp8266/CMakeLists.txt b/lib/ArduinoStreamUtils/extras/test/cores/esp8266/CMakeLists.txt new file mode 100644 index 00000000..94d385d5 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/cores/esp8266/CMakeLists.txt @@ -0,0 +1,22 @@ +# StreamUtils - github.com/bblanchon/ArduinoStreamUtils +# Copyright Benoit Blanchon 2019-2021 +# MIT License + +add_library(Esp8266Core + Client.h + EEPROM.cpp + EEPROM.h + Print.h + Stream.h + WString.h +) + +target_include_directories(Esp8266Core + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} +) + +target_compile_definitions(Esp8266Core + PUBLIC + ARDUINO_ARCH_ESP8266 +) diff --git a/lib/ArduinoStreamUtils/extras/test/cores/esp8266/Client.h b/lib/ArduinoStreamUtils/extras/test/cores/esp8266/Client.h new file mode 100644 index 00000000..1fc8d8f0 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/cores/esp8266/Client.h @@ -0,0 +1,26 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include "Stream.h" +#include "WString.h" + +using IPAddress = String; + +class Client : public Stream { + public: + virtual int connect(IPAddress ip, uint16_t port) = 0; + virtual int connect(const char *host, uint16_t port) = 0; + virtual size_t write(uint8_t) = 0; + virtual size_t write(const uint8_t *buf, size_t size) = 0; + virtual int available() = 0; + virtual int read() = 0; + virtual int read(uint8_t *buf, size_t size) = 0; + virtual int peek() = 0; + virtual void flush() = 0; + virtual void stop() = 0; + virtual uint8_t connected() = 0; + virtual operator bool() = 0; +}; diff --git a/lib/ArduinoStreamUtils/extras/test/cores/esp8266/EEPROM.cpp b/lib/ArduinoStreamUtils/extras/test/cores/esp8266/EEPROM.cpp new file mode 100644 index 00000000..fd0ef682 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/cores/esp8266/EEPROM.cpp @@ -0,0 +1,19 @@ +#include "EEPROM.h" + +#include // memcpy + +EEPROMClass EEPROM; +static uint8_t commitedData[512]; +static uint8_t pendingData[512]; + +uint8_t EEPROMClass::read(int address) { + return commitedData[address]; +} + +void EEPROMClass::write(int address, uint8_t value) { + pendingData[address] = value; +} + +void EEPROMClass::commit() { + memcpy(commitedData, pendingData, 512); +} \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/extras/test/cores/esp8266/EEPROM.h b/lib/ArduinoStreamUtils/extras/test/cores/esp8266/EEPROM.h new file mode 100644 index 00000000..63da03ba --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/cores/esp8266/EEPROM.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +class EEPROMClass { + public: + uint8_t read(int); + void write(int, uint8_t); + void commit(); +}; + +extern EEPROMClass EEPROM; diff --git a/lib/ArduinoStreamUtils/extras/test/cores/esp8266/Print.h b/lib/ArduinoStreamUtils/extras/test/cores/esp8266/Print.h new file mode 100644 index 00000000..2abf7369 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/cores/esp8266/Print.h @@ -0,0 +1,43 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include + +#include +#include + +struct Print { + virtual size_t write(const uint8_t *buffer, size_t size) = 0; + virtual size_t write(uint8_t data) = 0; + + size_t write(const char *buffer, size_t size) { + return write((const uint8_t *)buffer, size); + } + + size_t print(const String &s) { + return write(s.c_str(), s.length()); + } + + size_t print(const char *s) { + return write(s, std::strlen(s)); + } + + size_t println() { + return 0; + } + + template + size_t print(const T &value) { + return print(String(value)); + } + + template + size_t println(const T &value) { + return print(value); + } + + virtual void flush() {} +}; \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/extras/test/cores/esp8266/Stream.h b/lib/ArduinoStreamUtils/extras/test/cores/esp8266/Stream.h new file mode 100644 index 00000000..5dea8c51 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/cores/esp8266/Stream.h @@ -0,0 +1,45 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include "Print.h" + +struct Stream : Print { + virtual int available() = 0; + virtual int read() = 0; + virtual int peek() = 0; + + virtual size_t readBytes(char *buffer, size_t length) { + size_t count = 0; + while (count < length) { + int c = timedRead(); + if (c < 0) + break; + *buffer++ = (char)c; + count++; + } + return count; + } + + virtual size_t readBytes(uint8_t *buffer, size_t length) { + return readBytes((char *)buffer, length); + } + + virtual String readString() { + String result; + int c; + while ((c = timedRead()) >= 0) { + result += c; + } + return result; + } + + void setTimeout(unsigned long) {} + + protected: + int timedRead() { + return read(); + } +}; diff --git a/lib/ArduinoStreamUtils/extras/test/cores/esp8266/WString.h b/lib/ArduinoStreamUtils/extras/test/cores/esp8266/WString.h new file mode 100644 index 00000000..9023e414 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/cores/esp8266/WString.h @@ -0,0 +1,5 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#include "../avr/WString.h" \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/extras/test/cores/nrf52/Arduino.h b/lib/ArduinoStreamUtils/extras/test/cores/nrf52/Arduino.h new file mode 100644 index 00000000..e94f54ff --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/cores/nrf52/Arduino.h @@ -0,0 +1,18 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include +#include +#include +#include + +#include + +inline unsigned long millis() { + return static_cast(time(NULL)); +} + +inline void yield() {} \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/extras/test/cores/nrf52/CMakeLists.txt b/lib/ArduinoStreamUtils/extras/test/cores/nrf52/CMakeLists.txt new file mode 100644 index 00000000..85f8ae8c --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/cores/nrf52/CMakeLists.txt @@ -0,0 +1,15 @@ +# StreamUtils - github.com/bblanchon/ArduinoStreamUtils +# Copyright Benoit Blanchon 2019-2021 +# MIT License + +add_library(Nrf52Core INTERFACE) + +target_include_directories(Nrf52Core + INTERFACE + ${CMAKE_CURRENT_SOURCE_DIR} +) + +target_compile_definitions(Nrf52Core + INTERFACE + ARDUINO_ARCH_NRF52 +) diff --git a/lib/ArduinoStreamUtils/extras/test/cores/nrf52/Client.h b/lib/ArduinoStreamUtils/extras/test/cores/nrf52/Client.h new file mode 100644 index 00000000..1fc8d8f0 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/cores/nrf52/Client.h @@ -0,0 +1,26 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include "Stream.h" +#include "WString.h" + +using IPAddress = String; + +class Client : public Stream { + public: + virtual int connect(IPAddress ip, uint16_t port) = 0; + virtual int connect(const char *host, uint16_t port) = 0; + virtual size_t write(uint8_t) = 0; + virtual size_t write(const uint8_t *buf, size_t size) = 0; + virtual int available() = 0; + virtual int read() = 0; + virtual int read(uint8_t *buf, size_t size) = 0; + virtual int peek() = 0; + virtual void flush() = 0; + virtual void stop() = 0; + virtual uint8_t connected() = 0; + virtual operator bool() = 0; +}; diff --git a/lib/ArduinoStreamUtils/extras/test/cores/nrf52/Print.h b/lib/ArduinoStreamUtils/extras/test/cores/nrf52/Print.h new file mode 100644 index 00000000..2d02babf --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/cores/nrf52/Print.h @@ -0,0 +1,45 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include + +#include +#include + +struct Print { + virtual size_t write(const uint8_t *buffer, size_t size) = 0; + virtual size_t write(uint8_t data) = 0; + + virtual int availableForWrite() { + return 0; + } + + size_t write(const char *buffer, size_t size) { + return write((const uint8_t *)buffer, size); + } + + size_t print(const String &s) { + return write(s.c_str(), s.length()); + } + + size_t print(const char *s) { + return write(s, std::strlen(s)); + } + + size_t println() { + return 0; + } + + template + size_t print(const T &value) { + return print(String(value)); + } + + template + size_t println(const T &value) { + return print(value); + } +}; \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/extras/test/cores/nrf52/Stream.h b/lib/ArduinoStreamUtils/extras/test/cores/nrf52/Stream.h new file mode 100644 index 00000000..884dbabd --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/cores/nrf52/Stream.h @@ -0,0 +1,45 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include "Print.h" + +struct Stream : Print { + virtual int available() = 0; + virtual int read() = 0; + virtual int peek() = 0; + virtual void flush() {} + + size_t readBytes(char *buffer, size_t length) { + size_t count = 0; + while (count < length) { + int c = timedRead(); + if (c < 0) + break; + *buffer++ = (char)c; + count++; + } + return count; + } + + size_t readBytes(uint8_t *buffer, size_t length) { + return readBytes((char *)buffer, length); + } + + String readString() { + String result; + int c; + while ((c = timedRead()) >= 0) + result += c; + return result; + } + + void setTimeout(unsigned long) {} + + protected: + int timedRead() { + return read(); + } +}; diff --git a/lib/ArduinoStreamUtils/extras/test/cores/nrf52/WString.h b/lib/ArduinoStreamUtils/extras/test/cores/nrf52/WString.h new file mode 100644 index 00000000..9023e414 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/cores/nrf52/WString.h @@ -0,0 +1,5 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#include "../avr/WString.h" \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/extras/test/cores/samd/Arduino.h b/lib/ArduinoStreamUtils/extras/test/cores/samd/Arduino.h new file mode 100644 index 00000000..e94f54ff --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/cores/samd/Arduino.h @@ -0,0 +1,18 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include +#include +#include +#include + +#include + +inline unsigned long millis() { + return static_cast(time(NULL)); +} + +inline void yield() {} \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/extras/test/cores/samd/CMakeLists.txt b/lib/ArduinoStreamUtils/extras/test/cores/samd/CMakeLists.txt new file mode 100644 index 00000000..ffc774b1 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/cores/samd/CMakeLists.txt @@ -0,0 +1,15 @@ +# StreamUtils - github.com/bblanchon/ArduinoStreamUtils +# Copyright Benoit Blanchon 2019-2021 +# MIT License + +add_library(SamdCore INTERFACE) + +target_include_directories(SamdCore + INTERFACE + ${CMAKE_CURRENT_SOURCE_DIR} +) + +target_compile_definitions(SamdCore + INTERFACE + ARDUINO_ARCH_SAMD +) diff --git a/lib/ArduinoStreamUtils/extras/test/cores/samd/Client.h b/lib/ArduinoStreamUtils/extras/test/cores/samd/Client.h new file mode 100644 index 00000000..1fc8d8f0 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/cores/samd/Client.h @@ -0,0 +1,26 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include "Stream.h" +#include "WString.h" + +using IPAddress = String; + +class Client : public Stream { + public: + virtual int connect(IPAddress ip, uint16_t port) = 0; + virtual int connect(const char *host, uint16_t port) = 0; + virtual size_t write(uint8_t) = 0; + virtual size_t write(const uint8_t *buf, size_t size) = 0; + virtual int available() = 0; + virtual int read() = 0; + virtual int read(uint8_t *buf, size_t size) = 0; + virtual int peek() = 0; + virtual void flush() = 0; + virtual void stop() = 0; + virtual uint8_t connected() = 0; + virtual operator bool() = 0; +}; diff --git a/lib/ArduinoStreamUtils/extras/test/cores/samd/Print.h b/lib/ArduinoStreamUtils/extras/test/cores/samd/Print.h new file mode 100644 index 00000000..7d2c8a0b --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/cores/samd/Print.h @@ -0,0 +1,46 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include + +#include +#include + +struct Print { + virtual size_t write(const uint8_t *buffer, size_t size) = 0; + virtual size_t write(uint8_t data) = 0; + virtual void flush() {} + + virtual int availableForWrite() { + return 0; + } + + size_t write(const char *buffer, size_t size) { + return write((const uint8_t *)buffer, size); + } + + size_t print(const String &s) { + return write(s.c_str(), s.length()); + } + + size_t print(const char *s) { + return write(s, std::strlen(s)); + } + + size_t println() { + return 0; + } + + template + size_t print(const T &value) { + return print(String(value)); + } + + template + size_t println(const T &value) { + return print(value); + } +}; \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/extras/test/cores/samd/Stream.h b/lib/ArduinoStreamUtils/extras/test/cores/samd/Stream.h new file mode 100644 index 00000000..458f9c3a --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/cores/samd/Stream.h @@ -0,0 +1,44 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include "Print.h" + +struct Stream : Print { + virtual int available() = 0; + virtual int read() = 0; + virtual int peek() = 0; + + size_t readBytes(char *buffer, size_t length) { + size_t count = 0; + while (count < length) { + int c = timedRead(); + if (c < 0) + break; + *buffer++ = (char)c; + count++; + } + return count; + } + + size_t readBytes(uint8_t *buffer, size_t length) { + return readBytes((char *)buffer, length); + } + + String readString() { + String result; + int c; + while ((c = timedRead()) >= 0) + result += c; + return result; + } + + void setTimeout(unsigned long) {} + + protected: + int timedRead() { + return read(); + } +}; diff --git a/lib/ArduinoStreamUtils/extras/test/cores/samd/WString.h b/lib/ArduinoStreamUtils/extras/test/cores/samd/WString.h new file mode 100644 index 00000000..9023e414 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/cores/samd/WString.h @@ -0,0 +1,5 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#include "../avr/WString.h" \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/extras/test/cores/stm32/Arduino.h b/lib/ArduinoStreamUtils/extras/test/cores/stm32/Arduino.h new file mode 100644 index 00000000..e94f54ff --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/cores/stm32/Arduino.h @@ -0,0 +1,18 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include +#include +#include +#include + +#include + +inline unsigned long millis() { + return static_cast(time(NULL)); +} + +inline void yield() {} \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/extras/test/cores/stm32/CMakeLists.txt b/lib/ArduinoStreamUtils/extras/test/cores/stm32/CMakeLists.txt new file mode 100644 index 00000000..092f6753 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/cores/stm32/CMakeLists.txt @@ -0,0 +1,22 @@ +# StreamUtils - github.com/bblanchon/ArduinoStreamUtils +# Copyright Benoit Blanchon 2019-2021 +# MIT License + +add_library(Stm32Core + Client.h + EEPROM.cpp + EEPROM.h + Print.h + Stream.h + WString.h +) + +target_include_directories(Stm32Core + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} +) + +target_compile_definitions(Stm32Core + PUBLIC + ARDUINO_ARCH_STM32 +) diff --git a/lib/ArduinoStreamUtils/extras/test/cores/stm32/Client.h b/lib/ArduinoStreamUtils/extras/test/cores/stm32/Client.h new file mode 100644 index 00000000..907948de --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/cores/stm32/Client.h @@ -0,0 +1,28 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include "Stream.h" +#include "WString.h" + +using IPAddress = String; + +struct Client : Stream { + // Print + virtual size_t write(uint8_t) = 0; + virtual size_t write(const uint8_t *buf, size_t size) = 0; + + // Stream + virtual int available() = 0; + virtual int read() = 0; + virtual void flush() = 0; + + virtual int connect(IPAddress ip, uint16_t port) = 0; + virtual int connect(const char *host, uint16_t port) = 0; + virtual uint8_t connected() = 0; + virtual void stop() = 0; + virtual operator bool() = 0; + virtual int read(uint8_t *buf, size_t size) = 0; +}; diff --git a/lib/ArduinoStreamUtils/extras/test/cores/stm32/EEPROM.cpp b/lib/ArduinoStreamUtils/extras/test/cores/stm32/EEPROM.cpp new file mode 100644 index 00000000..83af6c18 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/cores/stm32/EEPROM.cpp @@ -0,0 +1,12 @@ +#include "EEPROM.h" + +EEPROMClass EEPROM; +static uint8_t data[512]; + +uint8_t EEPROMClass::read(int address) { + return data[address]; +} + +void EEPROMClass::write(int address, uint8_t value) { + data[address] = value; +} diff --git a/lib/ArduinoStreamUtils/extras/test/cores/stm32/EEPROM.h b/lib/ArduinoStreamUtils/extras/test/cores/stm32/EEPROM.h new file mode 100644 index 00000000..6da7e0c2 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/cores/stm32/EEPROM.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +class EEPROMClass { + public: + uint8_t read(int); + void write(int, uint8_t); +}; + +extern EEPROMClass EEPROM; diff --git a/lib/ArduinoStreamUtils/extras/test/cores/stm32/Print.h b/lib/ArduinoStreamUtils/extras/test/cores/stm32/Print.h new file mode 100644 index 00000000..e255ec63 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/cores/stm32/Print.h @@ -0,0 +1,41 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include +#include + +#include + +struct Print { + virtual size_t write(const uint8_t *buffer, size_t size) = 0; + virtual size_t write(uint8_t data) = 0; + + size_t write(const char *buffer, size_t size) { + return write((const uint8_t *)buffer, size); + } + + size_t print(const String &s) { + return write(s.c_str(), s.length()); + } + + size_t print(const char *s) { + return write(s, std::strlen(s)); + } + + size_t println() { + return 0; + } + + template + size_t print(const T &value) { + return print(String(value)); + } + + template + size_t println(const T &value) { + return print(value); + } +}; diff --git a/lib/ArduinoStreamUtils/extras/test/cores/stm32/Stream.h b/lib/ArduinoStreamUtils/extras/test/cores/stm32/Stream.h new file mode 100644 index 00000000..441eb574 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/cores/stm32/Stream.h @@ -0,0 +1,42 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include "Print.h" + +struct Stream : Print { + virtual int available() = 0; + virtual int read() = 0; + virtual int peek() = 0; + virtual void flush() = 0; + + virtual size_t readBytes(char *buffer, size_t length) { + size_t count = 0; + while (count < length) { + int c = timedRead(); + if (c < 0) + break; + *buffer++ = (char)c; + count++; + } + return count; + } + + String readString() { + String result; + int c; + while ((c = timedRead()) >= 0) { + result += static_cast(c); + } + return result; + } + + void setTimeout(unsigned long) {} + + protected: + int timedRead() { + return read(); + } +}; diff --git a/lib/ArduinoStreamUtils/extras/test/cores/stm32/WString.h b/lib/ArduinoStreamUtils/extras/test/cores/stm32/WString.h new file mode 100644 index 00000000..a4c911bd --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/cores/stm32/WString.h @@ -0,0 +1,43 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include + +class String : private std::string { + public: + String() {} + String(const String& s) : std::string(s) {} + String(String&& s) : std::string(std::move(s)) {} + String(const char* s) : std::string(s) {} + String(int n) : std::string(std::to_string(n)) {} + + String& operator=(const String& rhs) { + std::string::operator=(rhs); + return *this; + } + + using std::string::c_str; + using std::string::length; + using std::string::operator+=; + using std::string::operator[]; + + void remove(unsigned int index, unsigned int count) { + erase(begin() + index, begin() + index + count); + } + + void toCharArray(char* buf, unsigned int bufsize, + unsigned int index = 0) const { + copy(buf, bufsize, index); + } + + friend bool operator==(const String& lhs, const char* rhs) { + return static_cast(lhs) == rhs; + } + + friend std::ostream& operator<<(std::ostream& lhs, const String& rhs) { + return lhs << static_cast(rhs); + } +}; diff --git a/lib/ArduinoStreamUtils/extras/test/cores/teensy/Arduino.h b/lib/ArduinoStreamUtils/extras/test/cores/teensy/Arduino.h new file mode 100644 index 00000000..e94f54ff --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/cores/teensy/Arduino.h @@ -0,0 +1,18 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include +#include +#include +#include + +#include + +inline unsigned long millis() { + return static_cast(time(NULL)); +} + +inline void yield() {} \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/extras/test/cores/teensy/CMakeLists.txt b/lib/ArduinoStreamUtils/extras/test/cores/teensy/CMakeLists.txt new file mode 100644 index 00000000..2b8cb8be --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/cores/teensy/CMakeLists.txt @@ -0,0 +1,22 @@ +# StreamUtils - github.com/bblanchon/ArduinoStreamUtils +# Copyright Benoit Blanchon 2019-2021 +# MIT License + +add_library(TeensyCore + Client.h + EEPROM.cpp + EEPROM.h + Print.h + Stream.h + WString.h +) + +target_include_directories(TeensyCore + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} +) + +target_compile_definitions(TeensyCore + PUBLIC + CORE_TEENSY +) diff --git a/lib/ArduinoStreamUtils/extras/test/cores/teensy/Client.h b/lib/ArduinoStreamUtils/extras/test/cores/teensy/Client.h new file mode 100644 index 00000000..20dc08b6 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/cores/teensy/Client.h @@ -0,0 +1,31 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include "Stream.h" +#include "WString.h" + +using IPAddress = String; + +struct Client : Stream { + virtual int connect(IPAddress ip, uint16_t port) = 0; + virtual int connect(const char *host, uint16_t port) = 0; + virtual uint8_t connected() = 0; + virtual void stop() = 0; + virtual operator bool() = 0; + + // Already in Print + virtual size_t write(uint8_t) = 0; + virtual size_t write(const uint8_t *buf, size_t size) = 0; + virtual void flush() = 0; + + // Already in Stream + virtual int available() = 0; + virtual int read() = 0; + virtual int peek() = 0; + + // Curiously not in Stream + virtual int read(uint8_t *buf, size_t size) = 0; +}; diff --git a/lib/ArduinoStreamUtils/extras/test/cores/teensy/EEPROM.cpp b/lib/ArduinoStreamUtils/extras/test/cores/teensy/EEPROM.cpp new file mode 100644 index 00000000..2b2eb1a8 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/cores/teensy/EEPROM.cpp @@ -0,0 +1,12 @@ +#include "EEPROM.h" + +EEPROMClass EEPROM; +static uint8_t data[512]; + +uint8_t EEPROMClass::read(int address) { + return data[address]; +} + +void EEPROMClass::update(int address, uint8_t value) { + data[address] = value; +} diff --git a/lib/ArduinoStreamUtils/extras/test/cores/teensy/EEPROM.h b/lib/ArduinoStreamUtils/extras/test/cores/teensy/EEPROM.h new file mode 100644 index 00000000..b5e3360c --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/cores/teensy/EEPROM.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +class EEPROMClass { + public: + uint8_t read(int); + void update(int, uint8_t); + // void write(int, uint8_t); <- it exists but we want to use update() instead +}; + +extern EEPROMClass EEPROM; diff --git a/lib/ArduinoStreamUtils/extras/test/cores/teensy/Print.h b/lib/ArduinoStreamUtils/extras/test/cores/teensy/Print.h new file mode 100644 index 00000000..7d2c8a0b --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/cores/teensy/Print.h @@ -0,0 +1,46 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include + +#include +#include + +struct Print { + virtual size_t write(const uint8_t *buffer, size_t size) = 0; + virtual size_t write(uint8_t data) = 0; + virtual void flush() {} + + virtual int availableForWrite() { + return 0; + } + + size_t write(const char *buffer, size_t size) { + return write((const uint8_t *)buffer, size); + } + + size_t print(const String &s) { + return write(s.c_str(), s.length()); + } + + size_t print(const char *s) { + return write(s, std::strlen(s)); + } + + size_t println() { + return 0; + } + + template + size_t print(const T &value) { + return print(String(value)); + } + + template + size_t println(const T &value) { + return print(value); + } +}; \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/extras/test/cores/teensy/Stream.h b/lib/ArduinoStreamUtils/extras/test/cores/teensy/Stream.h new file mode 100644 index 00000000..6515a1e3 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/cores/teensy/Stream.h @@ -0,0 +1,42 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include "Print.h" + +struct Stream : Print { + virtual int available() = 0; + virtual int read() = 0; + virtual int peek() = 0; + virtual void flush() = 0; + + size_t readBytes(char *buffer, size_t length) { + size_t count = 0; + while (count < length) { + int c = timedRead(); + if (c < 0) + break; + *buffer++ = (char)c; + count++; + } + return count; + } + + String readString() { + String result; + int c; + while ((c = timedRead()) >= 0) { + result += static_cast(c); + } + return result; + } + + void setTimeout(unsigned long) {} + + private: + int timedRead() { + return read(); + } +}; diff --git a/lib/ArduinoStreamUtils/extras/test/cores/teensy/WString.h b/lib/ArduinoStreamUtils/extras/test/cores/teensy/WString.h new file mode 100644 index 00000000..f0b99e22 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/cores/teensy/WString.h @@ -0,0 +1,7 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include "../avr/WString.h" \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/extras/test/doctest.h b/lib/ArduinoStreamUtils/extras/test/doctest.h new file mode 100644 index 00000000..9f004da5 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/doctest.h @@ -0,0 +1,6000 @@ +// ====================================================================== lgtm [cpp/missing-header-guard] +// == DO NOT MODIFY THIS FILE BY HAND - IT IS AUTO GENERATED BY CMAKE! == +// ====================================================================== +// +// doctest.h - the lightest feature-rich C++ single-header testing framework for unit tests and TDD +// +// Copyright (c) 2016-2019 Viktor Kirilov +// +// Distributed under the MIT Software License +// See accompanying file LICENSE.txt or copy at +// https://opensource.org/licenses/MIT +// +// The documentation can be found at the library's page: +// https://github.com/onqtam/doctest/blob/master/doc/markdown/readme.md +// +// ================================================================================================= +// ================================================================================================= +// ================================================================================================= +// +// The library is heavily influenced by Catch - https://github.com/catchorg/Catch2 +// which uses the Boost Software License - Version 1.0 +// see here - https://github.com/catchorg/Catch2/blob/master/LICENSE.txt +// +// The concept of subcases (sections in Catch) and expression decomposition are from there. +// Some parts of the code are taken directly: +// - stringification - the detection of "ostream& operator<<(ostream&, const T&)" and StringMaker<> +// - the Approx() helper class for floating point comparison +// - colors in the console +// - breaking into a debugger +// - signal / SEH handling +// - timer +// - XmlWriter class - thanks to Phil Nash for allowing the direct reuse (AKA copy/paste) +// +// The expression decomposing templates are taken from lest - https://github.com/martinmoene/lest +// which uses the Boost Software License - Version 1.0 +// see here - https://github.com/martinmoene/lest/blob/master/LICENSE.txt +// +// ================================================================================================= +// ================================================================================================= +// ================================================================================================= + +#ifndef DOCTEST_LIBRARY_INCLUDED +#define DOCTEST_LIBRARY_INCLUDED + +// ================================================================================================= +// == VERSION ====================================================================================== +// ================================================================================================= + +#define DOCTEST_VERSION_MAJOR 2 +#define DOCTEST_VERSION_MINOR 3 +#define DOCTEST_VERSION_PATCH 1 +#define DOCTEST_VERSION_STR "2.3.1" + +#define DOCTEST_VERSION \ + (DOCTEST_VERSION_MAJOR * 10000 + DOCTEST_VERSION_MINOR * 100 + DOCTEST_VERSION_PATCH) + +// ================================================================================================= +// == COMPILER VERSION ============================================================================= +// ================================================================================================= + +// ideas for the version stuff are taken from here: https://github.com/cxxstuff/cxx_detect + +#define DOCTEST_COMPILER(MAJOR, MINOR, PATCH) ((MAJOR)*10000000 + (MINOR)*100000 + (PATCH)) + +// GCC/Clang and GCC/MSVC are mutually exclusive, but Clang/MSVC are not because of clang-cl... +#if defined(_MSC_VER) && defined(_MSC_FULL_VER) +#if _MSC_VER == _MSC_FULL_VER / 10000 +#define DOCTEST_MSVC DOCTEST_COMPILER(_MSC_VER / 100, _MSC_VER % 100, _MSC_FULL_VER % 10000) +#else // MSVC +#define DOCTEST_MSVC \ + DOCTEST_COMPILER(_MSC_VER / 100, (_MSC_FULL_VER / 100000) % 100, _MSC_FULL_VER % 100000) +#endif // MSVC +#endif // MSVC +#if defined(__clang__) && defined(__clang_minor__) +#define DOCTEST_CLANG DOCTEST_COMPILER(__clang_major__, __clang_minor__, __clang_patchlevel__) +#elif defined(__GNUC__) && defined(__GNUC_MINOR__) && defined(__GNUC_PATCHLEVEL__) && \ + !defined(__INTEL_COMPILER) +#define DOCTEST_GCC DOCTEST_COMPILER(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) +#endif // GCC + +#ifndef DOCTEST_MSVC +#define DOCTEST_MSVC 0 +#endif // DOCTEST_MSVC +#ifndef DOCTEST_CLANG +#define DOCTEST_CLANG 0 +#endif // DOCTEST_CLANG +#ifndef DOCTEST_GCC +#define DOCTEST_GCC 0 +#endif // DOCTEST_GCC + +// ================================================================================================= +// == COMPILER WARNINGS HELPERS ==================================================================== +// ================================================================================================= + +#if DOCTEST_CLANG +#define DOCTEST_PRAGMA_TO_STR(x) _Pragma(#x) +#define DOCTEST_CLANG_SUPPRESS_WARNING_PUSH _Pragma("clang diagnostic push") +#define DOCTEST_CLANG_SUPPRESS_WARNING(w) DOCTEST_PRAGMA_TO_STR(clang diagnostic ignored w) +#define DOCTEST_CLANG_SUPPRESS_WARNING_POP _Pragma("clang diagnostic pop") +#define DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH(w) \ + DOCTEST_CLANG_SUPPRESS_WARNING_PUSH DOCTEST_CLANG_SUPPRESS_WARNING(w) +#else // DOCTEST_CLANG +#define DOCTEST_CLANG_SUPPRESS_WARNING_PUSH +#define DOCTEST_CLANG_SUPPRESS_WARNING(w) +#define DOCTEST_CLANG_SUPPRESS_WARNING_POP +#define DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH(w) +#endif // DOCTEST_CLANG + +#if DOCTEST_GCC +#define DOCTEST_PRAGMA_TO_STR(x) _Pragma(#x) +#define DOCTEST_GCC_SUPPRESS_WARNING_PUSH _Pragma("GCC diagnostic push") +#define DOCTEST_GCC_SUPPRESS_WARNING(w) DOCTEST_PRAGMA_TO_STR(GCC diagnostic ignored w) +#define DOCTEST_GCC_SUPPRESS_WARNING_POP _Pragma("GCC diagnostic pop") +#define DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH(w) \ + DOCTEST_GCC_SUPPRESS_WARNING_PUSH DOCTEST_GCC_SUPPRESS_WARNING(w) +#else // DOCTEST_GCC +#define DOCTEST_GCC_SUPPRESS_WARNING_PUSH +#define DOCTEST_GCC_SUPPRESS_WARNING(w) +#define DOCTEST_GCC_SUPPRESS_WARNING_POP +#define DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH(w) +#endif // DOCTEST_GCC + +#if DOCTEST_MSVC +#define DOCTEST_MSVC_SUPPRESS_WARNING_PUSH __pragma(warning(push)) +#define DOCTEST_MSVC_SUPPRESS_WARNING(w) __pragma(warning(disable : w)) +#define DOCTEST_MSVC_SUPPRESS_WARNING_POP __pragma(warning(pop)) +#define DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(w) \ + DOCTEST_MSVC_SUPPRESS_WARNING_PUSH DOCTEST_MSVC_SUPPRESS_WARNING(w) +#else // DOCTEST_MSVC +#define DOCTEST_MSVC_SUPPRESS_WARNING_PUSH +#define DOCTEST_MSVC_SUPPRESS_WARNING(w) +#define DOCTEST_MSVC_SUPPRESS_WARNING_POP +#define DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(w) +#endif // DOCTEST_MSVC + +// ================================================================================================= +// == COMPILER WARNINGS ============================================================================ +// ================================================================================================= + +DOCTEST_CLANG_SUPPRESS_WARNING_PUSH +DOCTEST_CLANG_SUPPRESS_WARNING("-Wunknown-pragmas") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wnon-virtual-dtor") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wweak-vtables") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wpadded") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wdeprecated") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-prototypes") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-local-typedef") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat-pedantic") + +DOCTEST_GCC_SUPPRESS_WARNING_PUSH +DOCTEST_GCC_SUPPRESS_WARNING("-Wunknown-pragmas") +DOCTEST_GCC_SUPPRESS_WARNING("-Wpragmas") +DOCTEST_GCC_SUPPRESS_WARNING("-Weffc++") +DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-overflow") +DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-aliasing") +DOCTEST_GCC_SUPPRESS_WARNING("-Wctor-dtor-privacy") +DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-declarations") +DOCTEST_GCC_SUPPRESS_WARNING("-Wnon-virtual-dtor") +DOCTEST_GCC_SUPPRESS_WARNING("-Winline") +DOCTEST_GCC_SUPPRESS_WARNING("-Wunused-local-typedefs") +DOCTEST_GCC_SUPPRESS_WARNING("-Wuseless-cast") + +DOCTEST_MSVC_SUPPRESS_WARNING_PUSH +DOCTEST_MSVC_SUPPRESS_WARNING(4616) // invalid compiler warning +DOCTEST_MSVC_SUPPRESS_WARNING(4619) // invalid compiler warning +DOCTEST_MSVC_SUPPRESS_WARNING(4996) // The compiler encountered a deprecated declaration +DOCTEST_MSVC_SUPPRESS_WARNING(4706) // assignment within conditional expression +DOCTEST_MSVC_SUPPRESS_WARNING(4512) // 'class' : assignment operator could not be generated +DOCTEST_MSVC_SUPPRESS_WARNING(4127) // conditional expression is constant +DOCTEST_MSVC_SUPPRESS_WARNING(4820) // padding +DOCTEST_MSVC_SUPPRESS_WARNING(4625) // copy constructor was implicitly defined as deleted +DOCTEST_MSVC_SUPPRESS_WARNING(4626) // assignment operator was implicitly defined as deleted +DOCTEST_MSVC_SUPPRESS_WARNING(5027) // move assignment operator was implicitly defined as deleted +DOCTEST_MSVC_SUPPRESS_WARNING(5026) // move constructor was implicitly defined as deleted +DOCTEST_MSVC_SUPPRESS_WARNING(4623) // default constructor was implicitly defined as deleted +DOCTEST_MSVC_SUPPRESS_WARNING(4640) // construction of local static object is not thread-safe +// static analysis +DOCTEST_MSVC_SUPPRESS_WARNING(26439) // This kind of function may not throw. Declare it 'noexcept' +DOCTEST_MSVC_SUPPRESS_WARNING(26495) // Always initialize a member variable +DOCTEST_MSVC_SUPPRESS_WARNING(26451) // Arithmetic overflow ... +DOCTEST_MSVC_SUPPRESS_WARNING(26444) // Avoid unnamed objects with custom construction and dtr... + +// 4548 - expression before comma has no effect; expected expression with side - effect +// 4265 - class has virtual functions, but destructor is not virtual +// 4986 - exception specification does not match previous declaration +// 4350 - behavior change: 'member1' called instead of 'member2' +// 4668 - 'x' is not defined as a preprocessor macro, replacing with '0' for '#if/#elif' +// 4365 - conversion from 'int' to 'unsigned long', signed/unsigned mismatch +// 4774 - format string expected in argument 'x' is not a string literal +// 4820 - padding in structs + +// only 4 should be disabled globally: +// - 4514 # unreferenced inline function has been removed +// - 4571 # SEH related +// - 4710 # function not inlined +// - 4711 # function 'x' selected for automatic inline expansion + +#define DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN \ + DOCTEST_MSVC_SUPPRESS_WARNING_PUSH \ + DOCTEST_MSVC_SUPPRESS_WARNING(4548) \ + DOCTEST_MSVC_SUPPRESS_WARNING(4265) \ + DOCTEST_MSVC_SUPPRESS_WARNING(4986) \ + DOCTEST_MSVC_SUPPRESS_WARNING(4350) \ + DOCTEST_MSVC_SUPPRESS_WARNING(4668) \ + DOCTEST_MSVC_SUPPRESS_WARNING(4365) \ + DOCTEST_MSVC_SUPPRESS_WARNING(4774) \ + DOCTEST_MSVC_SUPPRESS_WARNING(4820) \ + DOCTEST_MSVC_SUPPRESS_WARNING(4625) \ + DOCTEST_MSVC_SUPPRESS_WARNING(4626) \ + DOCTEST_MSVC_SUPPRESS_WARNING(5027) \ + DOCTEST_MSVC_SUPPRESS_WARNING(5026) \ + DOCTEST_MSVC_SUPPRESS_WARNING(4623) \ + DOCTEST_MSVC_SUPPRESS_WARNING(5039) \ + DOCTEST_MSVC_SUPPRESS_WARNING(5045) + +#define DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END DOCTEST_MSVC_SUPPRESS_WARNING_POP + +// ================================================================================================= +// == FEATURE DETECTION ============================================================================ +// ================================================================================================= + +// general compiler feature support table: https://en.cppreference.com/w/cpp/compiler_support +// MSVC C++11 feature support table: https://msdn.microsoft.com/en-us/library/hh567368.aspx +// GCC C++11 feature support table: https://gcc.gnu.org/projects/cxx-status.html +// MSVC version table: +// https://en.wikipedia.org/wiki/Microsoft_Visual_C%2B%2B#Internal_version_numbering +// MSVC++ 14.2 (16) _MSC_VER == 1920 (Visual Studio 2019) << NOT YET RELEASED - April 2 2019 +// MSVC++ 14.1 (15) _MSC_VER == 1910 (Visual Studio 2017) +// MSVC++ 14.0 _MSC_VER == 1900 (Visual Studio 2015) +// MSVC++ 12.0 _MSC_VER == 1800 (Visual Studio 2013) +// MSVC++ 11.0 _MSC_VER == 1700 (Visual Studio 2012) +// MSVC++ 10.0 _MSC_VER == 1600 (Visual Studio 2010) +// MSVC++ 9.0 _MSC_VER == 1500 (Visual Studio 2008) +// MSVC++ 8.0 _MSC_VER == 1400 (Visual Studio 2005) + +#if DOCTEST_MSVC && !defined(DOCTEST_CONFIG_WINDOWS_SEH) +#define DOCTEST_CONFIG_WINDOWS_SEH +#endif // MSVC +#if defined(DOCTEST_CONFIG_NO_WINDOWS_SEH) && defined(DOCTEST_CONFIG_WINDOWS_SEH) +#undef DOCTEST_CONFIG_WINDOWS_SEH +#endif // DOCTEST_CONFIG_NO_WINDOWS_SEH + +#if !defined(_WIN32) && !defined(__QNX__) && !defined(DOCTEST_CONFIG_POSIX_SIGNALS) && \ + !defined(__EMSCRIPTEN__) +#define DOCTEST_CONFIG_POSIX_SIGNALS +#endif // _WIN32 +#if defined(DOCTEST_CONFIG_NO_POSIX_SIGNALS) && defined(DOCTEST_CONFIG_POSIX_SIGNALS) +#undef DOCTEST_CONFIG_POSIX_SIGNALS +#endif // DOCTEST_CONFIG_NO_POSIX_SIGNALS + +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS +#if !defined(__cpp_exceptions) && !defined(__EXCEPTIONS) && !defined(_CPPUNWIND) +#define DOCTEST_CONFIG_NO_EXCEPTIONS +#endif // no exceptions +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + +#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS +#define DOCTEST_CONFIG_NO_EXCEPTIONS +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS + +#if defined(DOCTEST_CONFIG_NO_EXCEPTIONS) && !defined(DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS) +#define DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS && !DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS + +#if defined(DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN) && !defined(DOCTEST_CONFIG_IMPLEMENT) +#define DOCTEST_CONFIG_IMPLEMENT +#endif // DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN + +#if defined(_WIN32) || defined(__CYGWIN__) +#if DOCTEST_MSVC +#define DOCTEST_SYMBOL_EXPORT __declspec(dllexport) +#define DOCTEST_SYMBOL_IMPORT __declspec(dllimport) +#else // MSVC +#define DOCTEST_SYMBOL_EXPORT __attribute__((dllexport)) +#define DOCTEST_SYMBOL_IMPORT __attribute__((dllimport)) +#endif // MSVC +#else // _WIN32 +#define DOCTEST_SYMBOL_EXPORT __attribute__((visibility("default"))) +#define DOCTEST_SYMBOL_IMPORT +#endif // _WIN32 + +#ifdef DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL +#ifdef DOCTEST_CONFIG_IMPLEMENT +#define DOCTEST_INTERFACE DOCTEST_SYMBOL_EXPORT +#else // DOCTEST_CONFIG_IMPLEMENT +#define DOCTEST_INTERFACE DOCTEST_SYMBOL_IMPORT +#endif // DOCTEST_CONFIG_IMPLEMENT +#else // DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL +#define DOCTEST_INTERFACE +#endif // DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL + +#define DOCTEST_EMPTY + +#if DOCTEST_MSVC +#define DOCTEST_NOINLINE __declspec(noinline) +#define DOCTEST_UNUSED +#define DOCTEST_ALIGNMENT(x) +#else // MSVC +#define DOCTEST_NOINLINE __attribute__((noinline)) +#define DOCTEST_UNUSED __attribute__((unused)) +#define DOCTEST_ALIGNMENT(x) __attribute__((aligned(x))) +#endif // MSVC + +#ifndef DOCTEST_CONFIG_NUM_CAPTURES_ON_STACK +#define DOCTEST_CONFIG_NUM_CAPTURES_ON_STACK 5 +#endif // DOCTEST_CONFIG_NUM_CAPTURES_ON_STACK + +// ================================================================================================= +// == FEATURE DETECTION END ======================================================================== +// ================================================================================================= + +// internal macros for string concatenation and anonymous variable name generation +#define DOCTEST_CAT_IMPL(s1, s2) s1##s2 +#define DOCTEST_CAT(s1, s2) DOCTEST_CAT_IMPL(s1, s2) +#ifdef __COUNTER__ // not standard and may be missing for some compilers +#define DOCTEST_ANONYMOUS(x) DOCTEST_CAT(x, __COUNTER__) +#else // __COUNTER__ +#define DOCTEST_ANONYMOUS(x) DOCTEST_CAT(x, __LINE__) +#endif // __COUNTER__ + +#define DOCTEST_TOSTR(x) #x + +#ifndef DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE +#define DOCTEST_REF_WRAP(x) x& +#else // DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE +#define DOCTEST_REF_WRAP(x) x +#endif // DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE + +// not using __APPLE__ because... this is how Catch does it +#ifdef __MAC_OS_X_VERSION_MIN_REQUIRED +#define DOCTEST_PLATFORM_MAC +#elif defined(__IPHONE_OS_VERSION_MIN_REQUIRED) +#define DOCTEST_PLATFORM_IPHONE +#elif defined(_WIN32) +#define DOCTEST_PLATFORM_WINDOWS +#else // DOCTEST_PLATFORM +#define DOCTEST_PLATFORM_LINUX +#endif // DOCTEST_PLATFORM + +// clang-format off +#define DOCTEST_DELETE_COPIES(type) type(const type&) = delete; type& operator=(const type&) = delete +#define DOCTEST_DECLARE_COPIES(type) type(const type&); type& operator=(const type&) +#define DOCTEST_DEFINE_COPIES(type) type::type(const type&) = default; type& type::operator=(const type&) = default +#define DOCTEST_DECLARE_DEFAULTS(type) type(); ~type() +#define DOCTEST_DEFINE_DEFAULTS(type) type::type() = default; type::~type() = default +// clang-format on + +#define DOCTEST_GLOBAL_NO_WARNINGS(var) \ + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wglobal-constructors") \ + static int var DOCTEST_UNUSED // NOLINT(fuchsia-statically-constructed-objects,cert-err58-cpp) +#define DOCTEST_GLOBAL_NO_WARNINGS_END() DOCTEST_CLANG_SUPPRESS_WARNING_POP + +// should probably take a look at https://github.com/scottt/debugbreak +#ifdef DOCTEST_PLATFORM_MAC +#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :) +#elif DOCTEST_MSVC +#define DOCTEST_BREAK_INTO_DEBUGGER() __debugbreak() +#elif defined(__MINGW32__) +extern "C" __declspec(dllimport) void __stdcall DebugBreak(); +#define DOCTEST_BREAK_INTO_DEBUGGER() ::DebugBreak() +#else // linux +#define DOCTEST_BREAK_INTO_DEBUGGER() ((void)0) +#endif // linux + +// this is kept here for backwards compatibility since the config option was changed +#ifdef DOCTEST_CONFIG_USE_IOSFWD +#define DOCTEST_CONFIG_USE_STD_HEADERS +#endif // DOCTEST_CONFIG_USE_IOSFWD + +#ifdef DOCTEST_CONFIG_USE_STD_HEADERS +#include +#include +#else // DOCTEST_CONFIG_USE_STD_HEADERS + +#if DOCTEST_CLANG +// to detect if libc++ is being used with clang (the _LIBCPP_VERSION identifier) +#include +#endif // clang + +#ifdef _LIBCPP_VERSION +#define DOCTEST_STD_NAMESPACE_BEGIN _LIBCPP_BEGIN_NAMESPACE_STD +#define DOCTEST_STD_NAMESPACE_END _LIBCPP_END_NAMESPACE_STD +#else // _LIBCPP_VERSION +#define DOCTEST_STD_NAMESPACE_BEGIN namespace std { +#define DOCTEST_STD_NAMESPACE_END } +#endif // _LIBCPP_VERSION + +// Forward declaring 'X' in namespace std is not permitted by the C++ Standard. +DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4643) + +DOCTEST_STD_NAMESPACE_BEGIN +typedef decltype(nullptr) nullptr_t; +template +struct char_traits; +template <> +struct char_traits; +template +class basic_ostream; +typedef basic_ostream> ostream; +template +class tuple; +DOCTEST_STD_NAMESPACE_END + +DOCTEST_MSVC_SUPPRESS_WARNING_POP + +#endif // DOCTEST_CONFIG_USE_STD_HEADERS + +#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS +#include +#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + +namespace doctest { + +DOCTEST_INTERFACE extern bool is_running_in_test; + +// A 24 byte string class (can be as small as 17 for x64 and 13 for x86) that can hold strings with length +// of up to 23 chars on the stack before going on the heap - the last byte of the buffer is used for: +// - "is small" bit - the highest bit - if "0" then it is small - otherwise its "1" (128) +// - if small - capacity left before going on the heap - using the lowest 5 bits +// - if small - 2 bits are left unused - the second and third highest ones +// - if small - acts as a null terminator if strlen() is 23 (24 including the null terminator) +// and the "is small" bit remains "0" ("as well as the capacity left") so its OK +// Idea taken from this lecture about the string implementation of facebook/folly - fbstring +// https://www.youtube.com/watch?v=kPR8h4-qZdk +// TODO: +// - optimizations - like not deleting memory unnecessarily in operator= and etc. +// - resize/reserve/clear +// - substr +// - replace +// - back/front +// - iterator stuff +// - find & friends +// - push_back/pop_back +// - assign/insert/erase +// - relational operators as free functions - taking const char* as one of the params +class DOCTEST_INTERFACE String +{ + static const unsigned len = 24; //!OCLINT avoid private static members + static const unsigned last = len - 1; //!OCLINT avoid private static members + + struct view // len should be more than sizeof(view) - because of the final byte for flags + { + char* ptr; + unsigned size; + unsigned capacity; + }; + + union + { + char buf[len]; + view data; + }; + + bool isOnStack() const { return (buf[last] & 128) == 0; } + void setOnHeap(); + void setLast(unsigned in = last); + + void copy(const String& other); + +public: + String(); + ~String(); + + // cppcheck-suppress noExplicitConstructor + String(const char* in); + String(const char* in, unsigned in_size); + + String(const String& other); + String& operator=(const String& other); + + String& operator+=(const String& other); + String operator+(const String& other) const; + + String(String&& other); + String& operator=(String&& other); + + char operator[](unsigned i) const; + char& operator[](unsigned i); + + // the only functions I'm willing to leave in the interface - available for inlining + const char* c_str() const { return const_cast(this)->c_str(); } // NOLINT + char* c_str() { + if(isOnStack()) + return reinterpret_cast(buf); + return data.ptr; + } + + unsigned size() const; + unsigned capacity() const; + + int compare(const char* other, bool no_case = false) const; + int compare(const String& other, bool no_case = false) const; +}; + +DOCTEST_INTERFACE bool operator==(const String& lhs, const String& rhs); +DOCTEST_INTERFACE bool operator!=(const String& lhs, const String& rhs); +DOCTEST_INTERFACE bool operator<(const String& lhs, const String& rhs); +DOCTEST_INTERFACE bool operator>(const String& lhs, const String& rhs); +DOCTEST_INTERFACE bool operator<=(const String& lhs, const String& rhs); +DOCTEST_INTERFACE bool operator>=(const String& lhs, const String& rhs); + +DOCTEST_INTERFACE std::ostream& operator<<(std::ostream& s, const String& in); + +namespace Color { + enum Enum + { + None = 0, + White, + Red, + Green, + Blue, + Cyan, + Yellow, + Grey, + + Bright = 0x10, + + BrightRed = Bright | Red, + BrightGreen = Bright | Green, + LightGrey = Bright | Grey, + BrightWhite = Bright | White + }; + + DOCTEST_INTERFACE std::ostream& operator<<(std::ostream& s, Color::Enum code); +} // namespace Color + +namespace assertType { + enum Enum + { + // macro traits + + is_warn = 1, + is_check = 2 * is_warn, + is_require = 2 * is_check, + + is_normal = 2 * is_require, + is_throws = 2 * is_normal, + is_throws_as = 2 * is_throws, + is_throws_with = 2 * is_throws_as, + is_nothrow = 2 * is_throws_with, + + is_false = 2 * is_nothrow, + is_unary = 2 * is_false, // not checked anywhere - used just to distinguish the types + + is_eq = 2 * is_unary, + is_ne = 2 * is_eq, + + is_lt = 2 * is_ne, + is_gt = 2 * is_lt, + + is_ge = 2 * is_gt, + is_le = 2 * is_ge, + + // macro types + + DT_WARN = is_normal | is_warn, + DT_CHECK = is_normal | is_check, + DT_REQUIRE = is_normal | is_require, + + DT_WARN_FALSE = is_normal | is_false | is_warn, + DT_CHECK_FALSE = is_normal | is_false | is_check, + DT_REQUIRE_FALSE = is_normal | is_false | is_require, + + DT_WARN_THROWS = is_throws | is_warn, + DT_CHECK_THROWS = is_throws | is_check, + DT_REQUIRE_THROWS = is_throws | is_require, + + DT_WARN_THROWS_AS = is_throws_as | is_warn, + DT_CHECK_THROWS_AS = is_throws_as | is_check, + DT_REQUIRE_THROWS_AS = is_throws_as | is_require, + + DT_WARN_THROWS_WITH = is_throws_with | is_warn, + DT_CHECK_THROWS_WITH = is_throws_with | is_check, + DT_REQUIRE_THROWS_WITH = is_throws_with | is_require, + + DT_WARN_NOTHROW = is_nothrow | is_warn, + DT_CHECK_NOTHROW = is_nothrow | is_check, + DT_REQUIRE_NOTHROW = is_nothrow | is_require, + + DT_WARN_EQ = is_normal | is_eq | is_warn, + DT_CHECK_EQ = is_normal | is_eq | is_check, + DT_REQUIRE_EQ = is_normal | is_eq | is_require, + + DT_WARN_NE = is_normal | is_ne | is_warn, + DT_CHECK_NE = is_normal | is_ne | is_check, + DT_REQUIRE_NE = is_normal | is_ne | is_require, + + DT_WARN_GT = is_normal | is_gt | is_warn, + DT_CHECK_GT = is_normal | is_gt | is_check, + DT_REQUIRE_GT = is_normal | is_gt | is_require, + + DT_WARN_LT = is_normal | is_lt | is_warn, + DT_CHECK_LT = is_normal | is_lt | is_check, + DT_REQUIRE_LT = is_normal | is_lt | is_require, + + DT_WARN_GE = is_normal | is_ge | is_warn, + DT_CHECK_GE = is_normal | is_ge | is_check, + DT_REQUIRE_GE = is_normal | is_ge | is_require, + + DT_WARN_LE = is_normal | is_le | is_warn, + DT_CHECK_LE = is_normal | is_le | is_check, + DT_REQUIRE_LE = is_normal | is_le | is_require, + + DT_WARN_UNARY = is_normal | is_unary | is_warn, + DT_CHECK_UNARY = is_normal | is_unary | is_check, + DT_REQUIRE_UNARY = is_normal | is_unary | is_require, + + DT_WARN_UNARY_FALSE = is_normal | is_false | is_unary | is_warn, + DT_CHECK_UNARY_FALSE = is_normal | is_false | is_unary | is_check, + DT_REQUIRE_UNARY_FALSE = is_normal | is_false | is_unary | is_require, + }; +} // namespace assertType + +DOCTEST_INTERFACE const char* assertString(assertType::Enum at); +DOCTEST_INTERFACE const char* failureString(assertType::Enum at); +DOCTEST_INTERFACE const char* skipPathFromFilename(const char* file); + +struct DOCTEST_INTERFACE TestCaseData +{ + const char* m_file; // the file in which the test was registered + unsigned m_line; // the line where the test was registered + const char* m_name; // name of the test case + const char* m_test_suite; // the test suite in which the test was added + const char* m_description; + bool m_skip; + bool m_may_fail; + bool m_should_fail; + int m_expected_failures; + double m_timeout; + + DOCTEST_DECLARE_DEFAULTS(TestCaseData); + DOCTEST_DECLARE_COPIES(TestCaseData); +}; + +struct DOCTEST_INTERFACE AssertData +{ + // common - for all asserts + const TestCaseData* m_test_case; + assertType::Enum m_at; + const char* m_file; + int m_line; + const char* m_expr; + bool m_failed; + + // exception-related - for all asserts + bool m_threw; + String m_exception; + + // for normal asserts + String m_decomp; + + // for specific exception-related asserts + bool m_threw_as; + const char* m_exception_type; + + DOCTEST_DECLARE_DEFAULTS(AssertData); + DOCTEST_DELETE_COPIES(AssertData); +}; + +struct DOCTEST_INTERFACE MessageData +{ + String m_string; + const char* m_file; + int m_line; + assertType::Enum m_severity; + + DOCTEST_DECLARE_DEFAULTS(MessageData); + DOCTEST_DELETE_COPIES(MessageData); +}; + +struct DOCTEST_INTERFACE SubcaseSignature +{ + const char* m_name; + const char* m_file; + int m_line; + + SubcaseSignature(const char* name, const char* file, int line); + + bool operator<(const SubcaseSignature& other) const; + + DOCTEST_DECLARE_DEFAULTS(SubcaseSignature); + DOCTEST_DECLARE_COPIES(SubcaseSignature); +}; + +struct DOCTEST_INTERFACE IContextScope +{ + DOCTEST_DELETE_COPIES(IContextScope); + + IContextScope(); + virtual ~IContextScope(); + virtual void stringify(std::ostream*) const = 0; +}; + +struct ContextOptions //!OCLINT too many fields +{ + std::ostream* cout; // stdout stream - std::cout by default + std::ostream* cerr; // stderr stream - std::cerr by default + String binary_name; // the test binary name + + // == parameters from the command line + String out; // output filename + String order_by; // how tests should be ordered + unsigned rand_seed; // the seed for rand ordering + + unsigned first; // the first (matching) test to be executed + unsigned last; // the last (matching) test to be executed + + int abort_after; // stop tests after this many failed assertions + int subcase_filter_levels; // apply the subcase filters for the first N levels + + bool success; // include successful assertions in output + bool case_sensitive; // if filtering should be case sensitive + bool exit; // if the program should be exited after the tests are ran/whatever + bool duration; // print the time duration of each test case + bool no_throw; // to skip exceptions-related assertion macros + bool no_exitcode; // if the framework should return 0 as the exitcode + bool no_run; // to not run the tests at all (can be done with an "*" exclude) + bool no_version; // to not print the version of the framework + bool no_colors; // if output to the console should be colorized + bool force_colors; // forces the use of colors even when a tty cannot be detected + bool no_breaks; // to not break into the debugger + bool no_skip; // don't skip test cases which are marked to be skipped + bool gnu_file_line; // if line numbers should be surrounded with :x: and not (x): + bool no_path_in_filenames; // if the path to files should be removed from the output + bool no_line_numbers; // if source code line numbers should be omitted from the output + bool no_skipped_summary; // don't print "skipped" in the summary !!! UNDOCUMENTED !!! + + bool help; // to print the help + bool version; // to print the version + bool count; // if only the count of matching tests is to be retreived + bool list_test_cases; // to list all tests matching the filters + bool list_test_suites; // to list all suites matching the filters + bool list_reporters; // lists all registered reporters + + DOCTEST_DECLARE_DEFAULTS(ContextOptions); + DOCTEST_DELETE_COPIES(ContextOptions); +}; + +namespace detail { +#if defined(DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING) || defined(DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS) + template + struct enable_if + {}; + + template + struct enable_if + { typedef TYPE type; }; +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING) || DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + + // clang-format off + template struct remove_reference { typedef T type; }; + template struct remove_reference { typedef T type; }; + template struct remove_reference { typedef T type; }; + + template struct remove_const { typedef T type; }; + template struct remove_const { typedef T type; }; + // clang-format on + + template + struct deferred_false + // cppcheck-suppress unusedStructMember + { static const bool value = false; }; + + namespace has_insertion_operator_impl { + typedef char no; + typedef char yes[2]; + + struct any_t + { + template + // cppcheck-suppress noExplicitConstructor + any_t(const DOCTEST_REF_WRAP(T)); + }; + + yes& testStreamable(std::ostream&); + no testStreamable(no); + + no operator<<(const std::ostream&, const any_t&); + + template + struct has_insertion_operator + { + static std::ostream& s; + static const DOCTEST_REF_WRAP(T) t; + static const bool value = sizeof(decltype(testStreamable(s << t))) == sizeof(yes); + }; + } // namespace has_insertion_operator_impl + + template + struct has_insertion_operator : has_insertion_operator_impl::has_insertion_operator + {}; + + DOCTEST_INTERFACE void my_memcpy(void* dest, const void* src, unsigned num); + + DOCTEST_INTERFACE std::ostream* getTlsOss(); // returns a thread-local ostringstream + DOCTEST_INTERFACE String getTlsOssResult(); + + template + struct StringMakerBase + { + template + static String convert(const DOCTEST_REF_WRAP(T)) { + return "{?}"; + } + }; + + template <> + struct StringMakerBase + { + template + static String convert(const DOCTEST_REF_WRAP(T) in) { + *getTlsOss() << in; + return getTlsOssResult(); + } + }; + + DOCTEST_INTERFACE String rawMemoryToString(const void* object, unsigned size); + + template + String rawMemoryToString(const DOCTEST_REF_WRAP(T) object) { + return rawMemoryToString(&object, sizeof(object)); + } + + template + const char* type_to_string() { + return "<>"; + } +} // namespace detail + +template +struct StringMaker : public detail::StringMakerBase::value> +{}; + +template +struct StringMaker +{ + template + static String convert(U* p) { + if(p) + return detail::rawMemoryToString(p); + return "NULL"; + } +}; + +template +struct StringMaker +{ + static String convert(R C::*p) { + if(p) + return detail::rawMemoryToString(p); + return "NULL"; + } +}; + +template +String toString(const DOCTEST_REF_WRAP(T) value) { + return StringMaker::convert(value); +} + +#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +DOCTEST_INTERFACE String toString(char* in); +DOCTEST_INTERFACE String toString(const char* in); +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +DOCTEST_INTERFACE String toString(bool in); +DOCTEST_INTERFACE String toString(float in); +DOCTEST_INTERFACE String toString(double in); +DOCTEST_INTERFACE String toString(double long in); + +DOCTEST_INTERFACE String toString(char in); +DOCTEST_INTERFACE String toString(char signed in); +DOCTEST_INTERFACE String toString(char unsigned in); +DOCTEST_INTERFACE String toString(int short in); +DOCTEST_INTERFACE String toString(int short unsigned in); +DOCTEST_INTERFACE String toString(int in); +DOCTEST_INTERFACE String toString(int unsigned in); +DOCTEST_INTERFACE String toString(int long in); +DOCTEST_INTERFACE String toString(int long unsigned in); +DOCTEST_INTERFACE String toString(int long long in); +DOCTEST_INTERFACE String toString(int long long unsigned in); +DOCTEST_INTERFACE String toString(std::nullptr_t in); + +class DOCTEST_INTERFACE Approx +{ +public: + explicit Approx(double value); + + DOCTEST_DECLARE_COPIES(Approx); + + Approx operator()(double value) const; + +#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + template + explicit Approx(const T& value, + typename detail::enable_if::value>::type* = + static_cast(nullptr)) { + *this = Approx(static_cast(value)); + } +#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + + Approx& epsilon(double newEpsilon); + +#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + template + typename detail::enable_if::value, Approx&>::type epsilon( + const T& newEpsilon) { + m_epsilon = static_cast(newEpsilon); + return *this; + } +#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + + Approx& scale(double newScale); + +#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + template + typename detail::enable_if::value, Approx&>::type scale( + const T& newScale) { + m_scale = static_cast(newScale); + return *this; + } +#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + + // clang-format off + DOCTEST_INTERFACE friend bool operator==(double lhs, const Approx & rhs); + DOCTEST_INTERFACE friend bool operator==(const Approx & lhs, double rhs); + DOCTEST_INTERFACE friend bool operator!=(double lhs, const Approx & rhs); + DOCTEST_INTERFACE friend bool operator!=(const Approx & lhs, double rhs); + DOCTEST_INTERFACE friend bool operator<=(double lhs, const Approx & rhs); + DOCTEST_INTERFACE friend bool operator<=(const Approx & lhs, double rhs); + DOCTEST_INTERFACE friend bool operator>=(double lhs, const Approx & rhs); + DOCTEST_INTERFACE friend bool operator>=(const Approx & lhs, double rhs); + DOCTEST_INTERFACE friend bool operator< (double lhs, const Approx & rhs); + DOCTEST_INTERFACE friend bool operator< (const Approx & lhs, double rhs); + DOCTEST_INTERFACE friend bool operator> (double lhs, const Approx & rhs); + DOCTEST_INTERFACE friend bool operator> (const Approx & lhs, double rhs); + + DOCTEST_INTERFACE friend String toString(const Approx& in); + +#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS +#define DOCTEST_APPROX_PREFIX \ + template friend typename detail::enable_if::value, bool>::type + + DOCTEST_APPROX_PREFIX operator==(const T& lhs, const Approx& rhs) { return operator==(double(lhs), rhs); } + DOCTEST_APPROX_PREFIX operator==(const Approx& lhs, const T& rhs) { return operator==(rhs, lhs); } + DOCTEST_APPROX_PREFIX operator!=(const T& lhs, const Approx& rhs) { return !operator==(lhs, rhs); } + DOCTEST_APPROX_PREFIX operator!=(const Approx& lhs, const T& rhs) { return !operator==(rhs, lhs); } + DOCTEST_APPROX_PREFIX operator<=(const T& lhs, const Approx& rhs) { return double(lhs) < rhs.m_value || lhs == rhs; } + DOCTEST_APPROX_PREFIX operator<=(const Approx& lhs, const T& rhs) { return lhs.m_value < double(rhs) || lhs == rhs; } + DOCTEST_APPROX_PREFIX operator>=(const T& lhs, const Approx& rhs) { return double(lhs) > rhs.m_value || lhs == rhs; } + DOCTEST_APPROX_PREFIX operator>=(const Approx& lhs, const T& rhs) { return lhs.m_value > double(rhs) || lhs == rhs; } + DOCTEST_APPROX_PREFIX operator< (const T& lhs, const Approx& rhs) { return double(lhs) < rhs.m_value && lhs != rhs; } + DOCTEST_APPROX_PREFIX operator< (const Approx& lhs, const T& rhs) { return lhs.m_value < double(rhs) && lhs != rhs; } + DOCTEST_APPROX_PREFIX operator> (const T& lhs, const Approx& rhs) { return double(lhs) > rhs.m_value && lhs != rhs; } + DOCTEST_APPROX_PREFIX operator> (const Approx& lhs, const T& rhs) { return lhs.m_value > double(rhs) && lhs != rhs; } +#undef DOCTEST_APPROX_PREFIX +#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + + // clang-format on + +private: + double m_epsilon; + double m_scale; + double m_value; +}; + +DOCTEST_INTERFACE String toString(const Approx& in); + +DOCTEST_INTERFACE const ContextOptions* getContextOptions(); + +#if !defined(DOCTEST_CONFIG_DISABLE) + +namespace detail { + // clang-format off +#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + template struct decay_array { typedef T type; }; + template struct decay_array { typedef T* type; }; + template struct decay_array { typedef T* type; }; + + template struct not_char_pointer { enum { value = 1 }; }; + template<> struct not_char_pointer { enum { value = 0 }; }; + template<> struct not_char_pointer { enum { value = 0 }; }; + + template struct can_use_op : public not_char_pointer::type> {}; +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + // clang-format on + + struct DOCTEST_INTERFACE TestFailureException + { + DOCTEST_DECLARE_DEFAULTS(TestFailureException); + DOCTEST_DECLARE_COPIES(TestFailureException); + }; + + DOCTEST_INTERFACE bool checkIfShouldThrow(assertType::Enum at); + DOCTEST_INTERFACE void throwException(); + + struct DOCTEST_INTERFACE Subcase + { + SubcaseSignature m_signature; + bool m_entered = false; + + Subcase(const char* name, const char* file, int line); + ~Subcase(); + + DOCTEST_DELETE_COPIES(Subcase); + + operator bool() const; + }; + + template + String stringifyBinaryExpr(const DOCTEST_REF_WRAP(L) lhs, const char* op, + const DOCTEST_REF_WRAP(R) rhs) { + return toString(lhs) + op + toString(rhs); + } + +#define DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(op, op_str, op_macro) \ + template \ + DOCTEST_NOINLINE Result operator op(const DOCTEST_REF_WRAP(R) rhs) { \ + bool res = op_macro(lhs, rhs); \ + if(m_at & assertType::is_false) \ + res = !res; \ + if(!res || doctest::getContextOptions()->success) \ + return Result(res, stringifyBinaryExpr(lhs, op_str, rhs)); \ + return Result(res); \ + } + + // more checks could be added - like in Catch: + // https://github.com/catchorg/Catch2/pull/1480/files + // https://github.com/catchorg/Catch2/pull/1481/files +#define DOCTEST_FORBIT_EXPRESSION(rt, op) \ + template \ + rt& operator op(const R&) { \ + static_assert(deferred_false::value, \ + "Expression Too Complex Please Rewrite As Binary Comparison!"); \ + return *this; \ + } + + struct DOCTEST_INTERFACE Result + { + bool m_passed; + String m_decomp; + + Result(bool passed, const String& decomposition = String()); + + DOCTEST_DECLARE_DEFAULTS(Result); + DOCTEST_DECLARE_COPIES(Result); + + // forbidding some expressions based on this table: http://en.cppreference.com/w/cpp/language/operator_precedence + DOCTEST_FORBIT_EXPRESSION(Result, &) + DOCTEST_FORBIT_EXPRESSION(Result, ^) + DOCTEST_FORBIT_EXPRESSION(Result, |) + DOCTEST_FORBIT_EXPRESSION(Result, &&) + DOCTEST_FORBIT_EXPRESSION(Result, ||) + DOCTEST_FORBIT_EXPRESSION(Result, ==) + DOCTEST_FORBIT_EXPRESSION(Result, !=) + DOCTEST_FORBIT_EXPRESSION(Result, <) + DOCTEST_FORBIT_EXPRESSION(Result, >) + DOCTEST_FORBIT_EXPRESSION(Result, <=) + DOCTEST_FORBIT_EXPRESSION(Result, >=) + DOCTEST_FORBIT_EXPRESSION(Result, =) + DOCTEST_FORBIT_EXPRESSION(Result, +=) + DOCTEST_FORBIT_EXPRESSION(Result, -=) + DOCTEST_FORBIT_EXPRESSION(Result, *=) + DOCTEST_FORBIT_EXPRESSION(Result, /=) + DOCTEST_FORBIT_EXPRESSION(Result, %=) + DOCTEST_FORBIT_EXPRESSION(Result, <<=) + DOCTEST_FORBIT_EXPRESSION(Result, >>=) + DOCTEST_FORBIT_EXPRESSION(Result, &=) + DOCTEST_FORBIT_EXPRESSION(Result, ^=) + DOCTEST_FORBIT_EXPRESSION(Result, |=) + }; + +#ifndef DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION + + DOCTEST_CLANG_SUPPRESS_WARNING_PUSH + DOCTEST_CLANG_SUPPRESS_WARNING("-Wsign-conversion") + DOCTEST_CLANG_SUPPRESS_WARNING("-Wsign-compare") + //DOCTEST_CLANG_SUPPRESS_WARNING("-Wdouble-promotion") + //DOCTEST_CLANG_SUPPRESS_WARNING("-Wconversion") + //DOCTEST_CLANG_SUPPRESS_WARNING("-Wfloat-equal") + + DOCTEST_GCC_SUPPRESS_WARNING_PUSH + DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-conversion") + DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-compare") + //DOCTEST_GCC_SUPPRESS_WARNING("-Wdouble-promotion") + //DOCTEST_GCC_SUPPRESS_WARNING("-Wconversion") + //DOCTEST_GCC_SUPPRESS_WARNING("-Wfloat-equal") + + DOCTEST_MSVC_SUPPRESS_WARNING_PUSH + // http://stackoverflow.com/questions/39479163 what's the difference between 4018 and 4389 + DOCTEST_MSVC_SUPPRESS_WARNING(4388) // signed/unsigned mismatch + DOCTEST_MSVC_SUPPRESS_WARNING(4389) // 'operator' : signed/unsigned mismatch + DOCTEST_MSVC_SUPPRESS_WARNING(4018) // 'expression' : signed/unsigned mismatch + //DOCTEST_MSVC_SUPPRESS_WARNING(4805) // 'operation' : unsafe mix of type 'type' and type 'type' in operation + +#endif // DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION + + // clang-format off +#ifndef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +#define DOCTEST_COMPARISON_RETURN_TYPE bool +#else // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +#define DOCTEST_COMPARISON_RETURN_TYPE typename enable_if::value || can_use_op::value, bool>::type + inline bool eq(const char* lhs, const char* rhs) { return String(lhs) == String(rhs); } + inline bool ne(const char* lhs, const char* rhs) { return String(lhs) != String(rhs); } + inline bool lt(const char* lhs, const char* rhs) { return String(lhs) < String(rhs); } + inline bool gt(const char* lhs, const char* rhs) { return String(lhs) > String(rhs); } + inline bool le(const char* lhs, const char* rhs) { return String(lhs) <= String(rhs); } + inline bool ge(const char* lhs, const char* rhs) { return String(lhs) >= String(rhs); } +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + // clang-format on + +#define DOCTEST_RELATIONAL_OP(name, op) \ + template \ + DOCTEST_COMPARISON_RETURN_TYPE name(const DOCTEST_REF_WRAP(L) lhs, \ + const DOCTEST_REF_WRAP(R) rhs) { \ + return lhs op rhs; \ + } + + DOCTEST_RELATIONAL_OP(eq, ==) + DOCTEST_RELATIONAL_OP(ne, !=) + DOCTEST_RELATIONAL_OP(lt, <) + DOCTEST_RELATIONAL_OP(gt, >) + DOCTEST_RELATIONAL_OP(le, <=) + DOCTEST_RELATIONAL_OP(ge, >=) + +#ifndef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +#define DOCTEST_CMP_EQ(l, r) l == r +#define DOCTEST_CMP_NE(l, r) l != r +#define DOCTEST_CMP_GT(l, r) l > r +#define DOCTEST_CMP_LT(l, r) l < r +#define DOCTEST_CMP_GE(l, r) l >= r +#define DOCTEST_CMP_LE(l, r) l <= r +#else // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +#define DOCTEST_CMP_EQ(l, r) eq(l, r) +#define DOCTEST_CMP_NE(l, r) ne(l, r) +#define DOCTEST_CMP_GT(l, r) gt(l, r) +#define DOCTEST_CMP_LT(l, r) lt(l, r) +#define DOCTEST_CMP_GE(l, r) ge(l, r) +#define DOCTEST_CMP_LE(l, r) le(l, r) +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + + template + // cppcheck-suppress copyCtorAndEqOperator + struct Expression_lhs + { + L lhs; + assertType::Enum m_at; + + explicit Expression_lhs(L in, assertType::Enum at) + : lhs(in) + , m_at(at) {} + + DOCTEST_NOINLINE operator Result() { + bool res = !!lhs; + if(m_at & assertType::is_false) //!OCLINT bitwise operator in conditional + res = !res; + + if(!res || getContextOptions()->success) + return Result(res, toString(lhs)); + return Result(res); + } + + // clang-format off + DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(==, " == ", DOCTEST_CMP_EQ) //!OCLINT bitwise operator in conditional + DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(!=, " != ", DOCTEST_CMP_NE) //!OCLINT bitwise operator in conditional + DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(>, " > ", DOCTEST_CMP_GT) //!OCLINT bitwise operator in conditional + DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(<, " < ", DOCTEST_CMP_LT) //!OCLINT bitwise operator in conditional + DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(>=, " >= ", DOCTEST_CMP_GE) //!OCLINT bitwise operator in conditional + DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(<=, " <= ", DOCTEST_CMP_LE) //!OCLINT bitwise operator in conditional + // clang-format on + + // forbidding some expressions based on this table: http://en.cppreference.com/w/cpp/language/operator_precedence + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, &) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, ^) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, |) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, &&) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, ||) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, =) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, +=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, -=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, *=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, /=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, %=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, <<=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, >>=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, &=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, ^=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, |=) + // these 2 are unfortunate because they should be allowed - they have higher precedence over the comparisons, but the + // ExpressionDecomposer class uses the left shift operator to capture the left operand of the binary expression... + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, <<) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, >>) + }; + +#ifndef DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION + + DOCTEST_CLANG_SUPPRESS_WARNING_POP + DOCTEST_MSVC_SUPPRESS_WARNING_POP + DOCTEST_GCC_SUPPRESS_WARNING_POP + +#endif // DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION + + struct DOCTEST_INTERFACE ExpressionDecomposer + { + assertType::Enum m_at; + + ExpressionDecomposer(assertType::Enum at); + + DOCTEST_DECLARE_DEFAULTS(ExpressionDecomposer); + DOCTEST_DELETE_COPIES(ExpressionDecomposer); + + // The right operator for capturing expressions is "<=" instead of "<<" (based on the operator precedence table) + // but then there will be warnings from GCC about "-Wparentheses" and since "_Pragma()" is problematic this will stay for now... + // https://github.com/catchorg/Catch2/issues/870 + // https://github.com/catchorg/Catch2/issues/565 + template + Expression_lhs operator<<(const DOCTEST_REF_WRAP(L) operand) { + return Expression_lhs(operand, m_at); + } + }; + + struct DOCTEST_INTERFACE TestSuite + { + const char* m_test_suite; + const char* m_description; + bool m_skip; + bool m_may_fail; + bool m_should_fail; + int m_expected_failures; + double m_timeout; + + DOCTEST_DECLARE_DEFAULTS(TestSuite); + DOCTEST_DECLARE_COPIES(TestSuite); + + TestSuite& operator*(const char* in); + + template + TestSuite& operator*(const T& in) { + in.fill(*this); + return *this; + } + }; + + typedef void (*funcType)(); + + struct DOCTEST_INTERFACE TestCase : public TestCaseData + { + funcType m_test; // a function pointer to the test case + + const char* m_type; // for templated test cases - gets appended to the real name + int m_template_id; // an ID used to distinguish between the different versions of a templated test case + String m_full_name; // contains the name (only for templated test cases!) + the template type + + TestCase(funcType test, const char* file, unsigned line, const TestSuite& test_suite, + const char* type = "", int template_id = -1); + + DOCTEST_DECLARE_DEFAULTS(TestCase); + + TestCase(const TestCase& other); + + DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(26434) // hides a non-virtual function + TestCase& operator=(const TestCase& other); + DOCTEST_MSVC_SUPPRESS_WARNING_POP + + TestCase& operator*(const char* in); + + template + TestCase& operator*(const T& in) { + in.fill(*this); + return *this; + } + + bool operator<(const TestCase& other) const; + }; + + // forward declarations of functions used by the macros + DOCTEST_INTERFACE int regTest(const TestCase& tc); + DOCTEST_INTERFACE int setTestSuite(const TestSuite& ts); + DOCTEST_INTERFACE bool isDebuggerActive(); + + namespace binaryAssertComparison { + enum Enum + { + eq = 0, + ne, + gt, + lt, + ge, + le + }; + } // namespace binaryAssertComparison + + // clang-format off + template struct RelationalComparator { bool operator()(const DOCTEST_REF_WRAP(L), const DOCTEST_REF_WRAP(R) ) const { return false; } }; + +#define DOCTEST_BINARY_RELATIONAL_OP(n, op) \ + template struct RelationalComparator { bool operator()(const DOCTEST_REF_WRAP(L) lhs, const DOCTEST_REF_WRAP(R) rhs) const { return op(lhs, rhs); } }; + // clang-format on + + DOCTEST_BINARY_RELATIONAL_OP(0, eq) + DOCTEST_BINARY_RELATIONAL_OP(1, ne) + DOCTEST_BINARY_RELATIONAL_OP(2, gt) + DOCTEST_BINARY_RELATIONAL_OP(3, lt) + DOCTEST_BINARY_RELATIONAL_OP(4, ge) + DOCTEST_BINARY_RELATIONAL_OP(5, le) + + struct DOCTEST_INTERFACE ResultBuilder : public AssertData + { + ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr, + const char* exception_type = ""); + + DOCTEST_DECLARE_DEFAULTS(ResultBuilder); + DOCTEST_DELETE_COPIES(ResultBuilder); + + void setResult(const Result& res); + + template + DOCTEST_NOINLINE void binary_assert(const DOCTEST_REF_WRAP(L) lhs, + const DOCTEST_REF_WRAP(R) rhs) { + m_failed = !RelationalComparator()(lhs, rhs); + if(m_failed || getContextOptions()->success) + m_decomp = stringifyBinaryExpr(lhs, ", ", rhs); + } + + template + DOCTEST_NOINLINE void unary_assert(const DOCTEST_REF_WRAP(L) val) { + m_failed = !val; + + if(m_at & assertType::is_false) //!OCLINT bitwise operator in conditional + m_failed = !m_failed; + + if(m_failed || getContextOptions()->success) + m_decomp = toString(val); + } + + void translateException(); + + bool log(); + void react() const; + }; + + namespace assertAction { + enum Enum + { + nothing = 0, + dbgbreak = 1, + shouldthrow = 2 + }; + } // namespace assertAction + + DOCTEST_INTERFACE void failed_out_of_a_testing_context(const AssertData& ad); + + DOCTEST_INTERFACE void decomp_assert(assertType::Enum at, const char* file, int line, + const char* expr, Result result); + +#define DOCTEST_ASSERT_OUT_OF_TESTS(decomp) \ + do { \ + if(!is_running_in_test) { \ + if(failed) { \ + ResultBuilder rb(at, file, line, expr); \ + rb.m_failed = failed; \ + rb.m_decomp = decomp; \ + failed_out_of_a_testing_context(rb); \ + if(isDebuggerActive() && !getContextOptions()->no_breaks) \ + DOCTEST_BREAK_INTO_DEBUGGER(); \ + if(checkIfShouldThrow(at)) \ + throwException(); \ + } \ + return; \ + } \ + } while(false) + +#define DOCTEST_ASSERT_IN_TESTS(decomp) \ + ResultBuilder rb(at, file, line, expr); \ + rb.m_failed = failed; \ + if(rb.m_failed || getContextOptions()->success) \ + rb.m_decomp = decomp; \ + if(rb.log()) \ + DOCTEST_BREAK_INTO_DEBUGGER(); \ + if(rb.m_failed && checkIfShouldThrow(at)) \ + throwException() + + template + DOCTEST_NOINLINE void binary_assert(assertType::Enum at, const char* file, int line, + const char* expr, const DOCTEST_REF_WRAP(L) lhs, + const DOCTEST_REF_WRAP(R) rhs) { + bool failed = !RelationalComparator()(lhs, rhs); + + // ################################################################################### + // IF THE DEBUGGER BREAKS HERE - GO 1 LEVEL UP IN THE CALLSTACK FOR THE FAILING ASSERT + // THIS IS THE EFFECT OF HAVING 'DOCTEST_CONFIG_SUPER_FAST_ASSERTS' DEFINED + // ################################################################################### + DOCTEST_ASSERT_OUT_OF_TESTS(stringifyBinaryExpr(lhs, ", ", rhs)); + DOCTEST_ASSERT_IN_TESTS(stringifyBinaryExpr(lhs, ", ", rhs)); + } + + template + DOCTEST_NOINLINE void unary_assert(assertType::Enum at, const char* file, int line, + const char* expr, const DOCTEST_REF_WRAP(L) val) { + bool failed = !val; + + if(at & assertType::is_false) //!OCLINT bitwise operator in conditional + failed = !failed; + + // ################################################################################### + // IF THE DEBUGGER BREAKS HERE - GO 1 LEVEL UP IN THE CALLSTACK FOR THE FAILING ASSERT + // THIS IS THE EFFECT OF HAVING 'DOCTEST_CONFIG_SUPER_FAST_ASSERTS' DEFINED + // ################################################################################### + DOCTEST_ASSERT_OUT_OF_TESTS(toString(val)); + DOCTEST_ASSERT_IN_TESTS(toString(val)); + } + + struct DOCTEST_INTERFACE IExceptionTranslator + { + DOCTEST_DELETE_COPIES(IExceptionTranslator); + + IExceptionTranslator(); + virtual ~IExceptionTranslator(); + virtual bool translate(String&) const = 0; + }; + + template + class ExceptionTranslator : public IExceptionTranslator //!OCLINT destructor of virtual class + { + public: + explicit ExceptionTranslator(String (*translateFunction)(T)) + : m_translateFunction(translateFunction) {} + + bool translate(String& res) const override { +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + try { + throw; // lgtm [cpp/rethrow-no-exception] + // cppcheck-suppress catchExceptionByValue + } catch(T ex) { // NOLINT + res = m_translateFunction(ex); //!OCLINT parameter reassignment + return true; + } catch(...) {} //!OCLINT - empty catch statement +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + ((void)res); // to silence -Wunused-parameter + return false; + } + + private: + String (*m_translateFunction)(T); + }; + + DOCTEST_INTERFACE void registerExceptionTranslatorImpl(const IExceptionTranslator* et); + + template + struct StringStreamBase + { + template + static void convert(std::ostream* s, const T& in) { + *s << toString(in); + } + + // always treat char* as a string in this context - no matter + // if DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING is defined + static void convert(std::ostream* s, const char* in) { *s << String(in); } + }; + + template <> + struct StringStreamBase + { + template + static void convert(std::ostream* s, const T& in) { + *s << in; + } + }; + + template + struct StringStream : public StringStreamBase::value> + {}; + + template + void toStream(std::ostream* s, const T& value) { + StringStream::convert(s, value); + } + +#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + DOCTEST_INTERFACE void toStream(std::ostream* s, char* in); + DOCTEST_INTERFACE void toStream(std::ostream* s, const char* in); +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + DOCTEST_INTERFACE void toStream(std::ostream* s, bool in); + DOCTEST_INTERFACE void toStream(std::ostream* s, float in); + DOCTEST_INTERFACE void toStream(std::ostream* s, double in); + DOCTEST_INTERFACE void toStream(std::ostream* s, double long in); + + DOCTEST_INTERFACE void toStream(std::ostream* s, char in); + DOCTEST_INTERFACE void toStream(std::ostream* s, char signed in); + DOCTEST_INTERFACE void toStream(std::ostream* s, char unsigned in); + DOCTEST_INTERFACE void toStream(std::ostream* s, int short in); + DOCTEST_INTERFACE void toStream(std::ostream* s, int short unsigned in); + DOCTEST_INTERFACE void toStream(std::ostream* s, int in); + DOCTEST_INTERFACE void toStream(std::ostream* s, int unsigned in); + DOCTEST_INTERFACE void toStream(std::ostream* s, int long in); + DOCTEST_INTERFACE void toStream(std::ostream* s, int long unsigned in); + DOCTEST_INTERFACE void toStream(std::ostream* s, int long long in); + DOCTEST_INTERFACE void toStream(std::ostream* s, int long long unsigned in); + + class DOCTEST_INTERFACE ContextBuilder + { + friend class ContextScope; + + struct DOCTEST_INTERFACE ICapture + { + DOCTEST_DELETE_COPIES(ICapture); + ICapture(); + virtual ~ICapture(); + virtual void toStream(std::ostream*) const = 0; + }; + + template + struct Capture : public ICapture //!OCLINT destructor of virtual class + { + const T* capture; + + explicit Capture(const T* in) + : capture(in) {} + void toStream(std::ostream* s) const override { detail::toStream(s, *capture); } + }; + + struct DOCTEST_INTERFACE Chunk + { + char buf[sizeof(Capture)] DOCTEST_ALIGNMENT( + 2 * sizeof(void*)); // place to construct a Capture + + DOCTEST_DECLARE_DEFAULTS(Chunk); + DOCTEST_DELETE_COPIES(Chunk); + }; + + struct DOCTEST_INTERFACE Node + { + Chunk chunk; + Node* next; + + DOCTEST_DECLARE_DEFAULTS(Node); + DOCTEST_DELETE_COPIES(Node); + }; + + Chunk stackChunks[DOCTEST_CONFIG_NUM_CAPTURES_ON_STACK]; + int numCaptures = 0; + Node* head = nullptr; + Node* tail = nullptr; + + ContextBuilder(ContextBuilder& other); + + ContextBuilder& operator=(const ContextBuilder&) = delete; + + void stringify(std::ostream* s) const; + + public: + ContextBuilder(); + ~ContextBuilder(); + + template + DOCTEST_NOINLINE ContextBuilder& operator<<(T& in) { + Capture temp(&in); + + // construct either on stack or on heap + // copy the bytes for the whole object - including the vtable because we cant construct + // the object directly in the buffer using placement new - need the header... + if(numCaptures < DOCTEST_CONFIG_NUM_CAPTURES_ON_STACK) { + my_memcpy(stackChunks[numCaptures].buf, &temp, sizeof(Chunk)); + } else { + auto curr = new Node; + curr->next = nullptr; + if(tail) { + tail->next = curr; + tail = curr; + } else { + head = tail = curr; + } + + my_memcpy(tail->chunk.buf, &temp, sizeof(Chunk)); + } + ++numCaptures; + return *this; + } + + template + ContextBuilder& operator<<(const T&&) { + static_assert(deferred_false::value, + "Cannot pass temporaries or rvalues to the streaming operator because it " + "caches pointers to the passed objects for lazy evaluation!"); + return *this; + } + }; + + class DOCTEST_INTERFACE ContextScope : public IContextScope + { + ContextBuilder contextBuilder; + + public: + explicit ContextScope(ContextBuilder& temp); + + DOCTEST_DELETE_COPIES(ContextScope); + + ~ContextScope(); + + void stringify(std::ostream* s) const; + }; + + struct DOCTEST_INTERFACE MessageBuilder : public MessageData + { + std::ostream* m_stream; + + MessageBuilder(const char* file, int line, assertType::Enum severity); + MessageBuilder() = delete; + ~MessageBuilder(); + + DOCTEST_DELETE_COPIES(MessageBuilder); + + template + MessageBuilder& operator<<(const T& in) { + toStream(m_stream, in); + return *this; + } + + bool log(); + void react(); + }; +} // namespace detail + +#define DOCTEST_DEFINE_DECORATOR(name, type, def) \ + struct name \ + { \ + type data; \ + name(type in = def) \ + : data(in) {} \ + void fill(detail::TestCase& state) const { state.DOCTEST_CAT(m_, name) = data; } \ + void fill(detail::TestSuite& state) const { state.DOCTEST_CAT(m_, name) = data; } \ + } + +DOCTEST_DEFINE_DECORATOR(test_suite, const char*, ""); +DOCTEST_DEFINE_DECORATOR(description, const char*, ""); +DOCTEST_DEFINE_DECORATOR(skip, bool, true); +DOCTEST_DEFINE_DECORATOR(timeout, double, 0); +DOCTEST_DEFINE_DECORATOR(may_fail, bool, true); +DOCTEST_DEFINE_DECORATOR(should_fail, bool, true); +DOCTEST_DEFINE_DECORATOR(expected_failures, int, 0); + +template +int registerExceptionTranslator(String (*translateFunction)(T)) { + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wexit-time-destructors") + static detail::ExceptionTranslator exceptionTranslator(translateFunction); + DOCTEST_CLANG_SUPPRESS_WARNING_POP + detail::registerExceptionTranslatorImpl(&exceptionTranslator); + return 0; +} + +} // namespace doctest + +// in a separate namespace outside of doctest because the DOCTEST_TEST_SUITE macro +// introduces an anonymous namespace in which getCurrentTestSuite gets overridden +namespace doctest_detail_test_suite_ns { +DOCTEST_INTERFACE doctest::detail::TestSuite& getCurrentTestSuite(); +} // namespace doctest_detail_test_suite_ns + +namespace doctest { +#else // DOCTEST_CONFIG_DISABLE +template +int registerExceptionTranslator(String (*)(T)) { + return 0; +} +#endif // DOCTEST_CONFIG_DISABLE + +namespace detail { + typedef void (*assert_handler)(const AssertData&); + struct ContextState; +} // namespace detail + +class DOCTEST_INTERFACE Context +{ + detail::ContextState* p; + + void parseArgs(int argc, const char* const* argv, bool withDefaults = false); + +public: + explicit Context(int argc = 0, const char* const* argv = nullptr); + + DOCTEST_DELETE_COPIES(Context); + + ~Context(); + + void applyCommandLine(int argc, const char* const* argv); + + void addFilter(const char* filter, const char* value); + void clearFilters(); + void setOption(const char* option, int value); + void setOption(const char* option, const char* value); + + bool shouldExit(); + + void setAsDefaultForAssertsOutOfTestCases(); + + void setAssertHandler(detail::assert_handler ah); + + int run(); +}; + +namespace TestCaseFailureReason { + enum Enum + { + None = 0, + AssertFailure = 1, // an assertion has failed in the test case + Exception = 2, // test case threw an exception + Crash = 4, // a crash... + TooManyFailedAsserts = 8, // the abort-after option + Timeout = 16, // see the timeout decorator + ShouldHaveFailedButDidnt = 32, // see the should_fail decorator + ShouldHaveFailedAndDid = 64, // see the should_fail decorator + DidntFailExactlyNumTimes = 128, // see the expected_failures decorator + FailedExactlyNumTimes = 256, // see the expected_failures decorator + CouldHaveFailedAndDid = 512 // see the may_fail decorator + }; +} // namespace TestCaseFailureReason + +struct DOCTEST_INTERFACE CurrentTestCaseStats +{ + int numAssertsCurrentTest; + int numAssertsFailedCurrentTest; + double seconds; + int failure_flags; // use TestCaseFailureReason::Enum + + DOCTEST_DECLARE_DEFAULTS(CurrentTestCaseStats); + DOCTEST_DELETE_COPIES(CurrentTestCaseStats); +}; + +struct DOCTEST_INTERFACE TestCaseException +{ + String error_string; + bool is_crash; +}; + +struct DOCTEST_INTERFACE TestRunStats +{ + unsigned numTestCases; + unsigned numTestCasesPassingFilters; + unsigned numTestSuitesPassingFilters; + unsigned numTestCasesFailed; + int numAsserts; + int numAssertsFailed; + + DOCTEST_DECLARE_DEFAULTS(TestRunStats); + DOCTEST_DELETE_COPIES(TestRunStats); +}; + +struct QueryData +{ + const TestRunStats* run_stats = nullptr; + String* data = nullptr; + unsigned num_data = 0; +}; + +struct DOCTEST_INTERFACE IReporter +{ + // The constructor has to accept "const ContextOptions&" as a single argument + // which has most of the options for the run + a pointer to the stdout stream + // Reporter(const ContextOptions& in) + + // called when a query should be reported (listing test cases, printing the version, etc.) + virtual void report_query(const QueryData&) = 0; + + // called when the whole test run starts + virtual void test_run_start() = 0; + // called when the whole test run ends (caching a pointer to the input doesn't make sense here) + virtual void test_run_end(const TestRunStats&) = 0; + + // called when a test case is started (safe to cache a pointer to the input) + virtual void test_case_start(const TestCaseData&) = 0; + // called when a test case has ended + virtual void test_case_end(const CurrentTestCaseStats&) = 0; + + // called when an exception is thrown from the test case (or it crashes) + virtual void test_case_exception(const TestCaseException&) = 0; + + // called whenever a subcase is entered (don't cache pointers to the input) + virtual void subcase_start(const SubcaseSignature&) = 0; + // called whenever a subcase is exited (don't cache pointers to the input) + virtual void subcase_end() = 0; + + // called for each assert (don't cache pointers to the input) + virtual void log_assert(const AssertData&) = 0; + // called for each message (don't cache pointers to the input) + virtual void log_message(const MessageData&) = 0; + + // called when a test case is skipped either because it doesn't pass the filters, has a skip decorator + // or isn't in the execution range (between first and last) (safe to cache a pointer to the input) + virtual void test_case_skipped(const TestCaseData&) = 0; + + // doctest will not be managing the lifetimes of reporters given to it but this would still be nice to have + virtual ~IReporter(); + + // can obtain all currently active contexts and stringify them if one wishes to do so + static int get_num_active_contexts(); + static const IContextScope* const* get_active_contexts(); + + // can iterate through contexts which have been stringified automatically in their destructors when an exception has been thrown + static int get_num_stringified_contexts(); + static const String* get_stringified_contexts(); +}; + +namespace detail { + typedef IReporter* (*reporterCreatorFunc)(const ContextOptions&); + + DOCTEST_INTERFACE void registerReporterImpl(const char* name, int prio, reporterCreatorFunc c); + + template + IReporter* reporterCreator(const ContextOptions& o) { + return new Reporter(o); + } +} // namespace detail + +template +int registerReporter(const char* name, int priority) { + detail::registerReporterImpl(name, priority, detail::reporterCreator); + return 0; +} +} // namespace doctest + +// if registering is not disabled +#if !defined(DOCTEST_CONFIG_DISABLE) + +// common code in asserts - for convenience +#define DOCTEST_ASSERT_LOG_AND_REACT(b) \ + if(b.log()) \ + DOCTEST_BREAK_INTO_DEBUGGER(); \ + b.react() + +#ifdef DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS +#define DOCTEST_WRAP_IN_TRY(x) x; +#else // DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS +#define DOCTEST_WRAP_IN_TRY(x) \ + try { \ + x; \ + } catch(...) { _DOCTEST_RB.translateException(); } +#endif // DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS + +// registers the test by initializing a dummy var with a function +#define DOCTEST_REGISTER_FUNCTION(global_prefix, f, decorators) \ + global_prefix DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_VAR_)) = \ + doctest::detail::regTest( \ + doctest::detail::TestCase( \ + f, __FILE__, __LINE__, \ + doctest_detail_test_suite_ns::getCurrentTestSuite()) * \ + decorators); \ + DOCTEST_GLOBAL_NO_WARNINGS_END() + +#define DOCTEST_IMPLEMENT_FIXTURE(der, base, func, decorators) \ + namespace { \ + struct der : public base \ + { \ + void f(); \ + }; \ + static void func() { \ + der v; \ + v.f(); \ + } \ + DOCTEST_REGISTER_FUNCTION(DOCTEST_EMPTY, func, decorators) \ + } \ + inline DOCTEST_NOINLINE void der::f() + +#define DOCTEST_CREATE_AND_REGISTER_FUNCTION(f, decorators) \ + static void f(); \ + DOCTEST_REGISTER_FUNCTION(DOCTEST_EMPTY, f, decorators) \ + static void f() + +#define DOCTEST_CREATE_AND_REGISTER_FUNCTION_IN_CLASS(f, proxy, decorators) \ + static doctest::detail::funcType proxy() { return f; } \ + DOCTEST_REGISTER_FUNCTION(inline const, proxy(), decorators) \ + static void f() + +// for registering tests +#define DOCTEST_TEST_CASE(decorators) \ + DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), decorators) + +// for registering tests in classes - requires C++17 for inline variables! +#if __cplusplus >= 201703L || (DOCTEST_MSVC >= DOCTEST_COMPILER(19, 12, 0) && _MSVC_LANG >= 201703L) +#define DOCTEST_TEST_CASE_CLASS(decorators) \ + DOCTEST_CREATE_AND_REGISTER_FUNCTION_IN_CLASS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), \ + DOCTEST_ANONYMOUS(_DOCTEST_ANON_PROXY_), \ + decorators) +#else // DOCTEST_TEST_CASE_CLASS +#define DOCTEST_TEST_CASE_CLASS(...) \ + TEST_CASES_CAN_BE_REGISTERED_IN_CLASSES_ONLY_IN_CPP17_MODE_OR_WITH_VS_2017_OR_NEWER +#endif // DOCTEST_TEST_CASE_CLASS + +// for registering tests with a fixture +#define DOCTEST_TEST_CASE_FIXTURE(c, decorators) \ + DOCTEST_IMPLEMENT_FIXTURE(DOCTEST_ANONYMOUS(_DOCTEST_ANON_CLASS_), c, \ + DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), decorators) + +// for converting types to strings without the header and demangling +#define DOCTEST_TYPE_TO_STRING_IMPL(...) \ + template <> \ + inline const char* type_to_string<__VA_ARGS__>() { \ + return "<" #__VA_ARGS__ ">"; \ + } +#define DOCTEST_TYPE_TO_STRING(...) \ + namespace doctest { namespace detail { \ + DOCTEST_TYPE_TO_STRING_IMPL(__VA_ARGS__) \ + } \ + } \ + typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +// for typed tests +#define DOCTEST_REGISTER_TYPED_TEST_CASE_IMPL(func, type, decorators, idx) \ + doctest::detail::regTest( \ + doctest::detail::TestCase(func, __FILE__, __LINE__, \ + doctest_detail_test_suite_ns::getCurrentTestSuite(), \ + doctest::detail::type_to_string(), idx) * \ + decorators) + +#define DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, iter, func) \ + template \ + inline void func(); \ + template \ + struct iter; \ + template \ + struct iter> \ + { \ + iter(int line, int index) { \ + DOCTEST_REGISTER_TYPED_TEST_CASE_IMPL(func, Type, dec, line * 1000 + index); \ + iter>(line, index + 1); \ + } \ + }; \ + template <> \ + struct iter> \ + { \ + iter(int, int) {} \ + }; \ + template \ + inline void func() + +#define DOCTEST_TEST_CASE_TEMPLATE_DEFINE(dec, T, id) \ + DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, DOCTEST_CAT(id, ITERATOR), \ + DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_)) + +#define DOCTEST_TEST_CASE_TEMPLATE_INVOKE_IMPL(id, anon, ...) \ + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_CAT(anon, DUMMY)) = [] { \ + DOCTEST_CAT(id, ITERATOR)> DOCTEST_UNUSED DOCTEST_CAT( \ + anon, inner_dummy)(__LINE__, 0); \ + return 0; \ + }(); \ + DOCTEST_GLOBAL_NO_WARNINGS_END() + +#define DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, ...) \ + DOCTEST_TEST_CASE_TEMPLATE_INVOKE_IMPL(id, DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_), __VA_ARGS__) \ + typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +#define DOCTEST_TEST_CASE_TEMPLATE_APPLY_IMPL(id, anon, ...) \ + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_CAT(anon, DUMMY)) = [] { \ + DOCTEST_CAT(id, ITERATOR)<__VA_ARGS__> DOCTEST_UNUSED DOCTEST_CAT(anon, inner_dummy)( \ + __LINE__, 0); \ + return 0; \ + }(); \ + DOCTEST_GLOBAL_NO_WARNINGS_END() + +#define DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, ...) \ + DOCTEST_TEST_CASE_TEMPLATE_APPLY_IMPL(id, DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_), __VA_ARGS__) \ + typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +#define DOCTEST_TEST_CASE_TEMPLATE_IMPL(dec, T, anon, ...) \ + DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, DOCTEST_CAT(anon, ITERATOR), anon); \ + DOCTEST_TEST_CASE_TEMPLATE_INVOKE_IMPL(anon, anon, __VA_ARGS__) \ + template \ + inline void anon() + +#define DOCTEST_TEST_CASE_TEMPLATE(dec, T, ...) \ + DOCTEST_TEST_CASE_TEMPLATE_IMPL(dec, T, DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_), __VA_ARGS__) + +// for subcases +#define DOCTEST_SUBCASE(name) \ + if(const doctest::detail::Subcase & DOCTEST_ANONYMOUS(_DOCTEST_ANON_SUBCASE_) DOCTEST_UNUSED = \ + doctest::detail::Subcase(name, __FILE__, __LINE__)) + +// for grouping tests in test suites by using code blocks +#define DOCTEST_TEST_SUITE_IMPL(decorators, ns_name) \ + namespace ns_name { namespace doctest_detail_test_suite_ns { \ + static DOCTEST_NOINLINE doctest::detail::TestSuite& getCurrentTestSuite() { \ + DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4640) \ + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wexit-time-destructors") \ + static doctest::detail::TestSuite data; \ + static bool inited = false; \ + DOCTEST_MSVC_SUPPRESS_WARNING_POP \ + DOCTEST_CLANG_SUPPRESS_WARNING_POP \ + if(!inited) { \ + data* decorators; \ + inited = true; \ + } \ + return data; \ + } \ + } \ + } \ + namespace ns_name + +#define DOCTEST_TEST_SUITE(decorators) \ + DOCTEST_TEST_SUITE_IMPL(decorators, DOCTEST_ANONYMOUS(_DOCTEST_ANON_SUITE_)) + +// for starting a testsuite block +#define DOCTEST_TEST_SUITE_BEGIN(decorators) \ + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_VAR_)) = \ + doctest::detail::setTestSuite(doctest::detail::TestSuite() * decorators); \ + DOCTEST_GLOBAL_NO_WARNINGS_END() \ + typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +// for ending a testsuite block +#define DOCTEST_TEST_SUITE_END \ + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_VAR_)) = \ + doctest::detail::setTestSuite(doctest::detail::TestSuite() * ""); \ + DOCTEST_GLOBAL_NO_WARNINGS_END() \ + typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +// for registering exception translators +#define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR_IMPL(translatorName, signature) \ + inline doctest::String translatorName(signature); \ + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_TRANSLATOR_)) = \ + doctest::registerExceptionTranslator(translatorName); \ + DOCTEST_GLOBAL_NO_WARNINGS_END() \ + doctest::String translatorName(signature) + +#define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR(signature) \ + DOCTEST_REGISTER_EXCEPTION_TRANSLATOR_IMPL(DOCTEST_ANONYMOUS(_DOCTEST_ANON_TRANSLATOR_), \ + signature) + +// for registering +#define DOCTEST_REGISTER_REPORTER(name, priority, reporter) \ + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_REPORTER_)) = \ + doctest::registerReporter(name, priority); \ + DOCTEST_GLOBAL_NO_WARNINGS_END() typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +// for logging +#define DOCTEST_INFO(x) \ + doctest::detail::ContextScope DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_)( \ + doctest::detail::ContextBuilder() << x) +#define DOCTEST_CAPTURE(x) DOCTEST_INFO(#x " := " << x) + +#define DOCTEST_ADD_AT_IMPL(type, file, line, mb, x) \ + do { \ + doctest::detail::MessageBuilder mb(file, line, doctest::assertType::type); \ + mb << x; \ + DOCTEST_ASSERT_LOG_AND_REACT(mb); \ + } while((void)0, 0) + +// clang-format off +#define DOCTEST_ADD_MESSAGE_AT(file, line, x) DOCTEST_ADD_AT_IMPL(is_warn, file, line, DOCTEST_ANONYMOUS(_DOCTEST_MESSAGE_), x) +#define DOCTEST_ADD_FAIL_CHECK_AT(file, line, x) DOCTEST_ADD_AT_IMPL(is_check, file, line, DOCTEST_ANONYMOUS(_DOCTEST_MESSAGE_), x) +#define DOCTEST_ADD_FAIL_AT(file, line, x) DOCTEST_ADD_AT_IMPL(is_require, file, line, DOCTEST_ANONYMOUS(_DOCTEST_MESSAGE_), x) +// clang-format on + +#define DOCTEST_MESSAGE(x) DOCTEST_ADD_MESSAGE_AT(__FILE__, __LINE__, x) +#define DOCTEST_FAIL_CHECK(x) DOCTEST_ADD_FAIL_CHECK_AT(__FILE__, __LINE__, x) +#define DOCTEST_FAIL(x) DOCTEST_ADD_FAIL_AT(__FILE__, __LINE__, x) + +// hack for macros like INFO() that require lvalues +#if __cplusplus >= 201402L || (DOCTEST_MSVC >= DOCTEST_COMPILER(19, 10, 0)) +template +constexpr T to_lvalue = x; +#define DOCTEST_TO_LVALUE(...) to_lvalue +#else // TO_LVALUE +#define DOCTEST_TO_LVALUE(...) TO_LVALUE_CAN_BE_USED_ONLY_IN_CPP14_MODE_OR_WITH_VS_2017_OR_NEWER +#endif // TO_LVALUE + +#ifndef DOCTEST_CONFIG_SUPER_FAST_ASSERTS + +#define DOCTEST_ASSERT_IMPLEMENT_2(assert_type, ...) \ + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Woverloaded-shift-op-parentheses") \ + doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + __LINE__, #__VA_ARGS__); \ + DOCTEST_WRAP_IN_TRY(_DOCTEST_RB.setResult( \ + doctest::detail::ExpressionDecomposer(doctest::assertType::assert_type) \ + << __VA_ARGS__)) \ + DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB) \ + DOCTEST_CLANG_SUPPRESS_WARNING_POP + +#define DOCTEST_ASSERT_IMPLEMENT_1(assert_type, ...) \ + do { \ + DOCTEST_ASSERT_IMPLEMENT_2(assert_type, __VA_ARGS__); \ + } while((void)0, 0) + +#else // DOCTEST_CONFIG_SUPER_FAST_ASSERTS + +#define DOCTEST_ASSERT_IMPLEMENT_1(assert_type, ...) \ + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Woverloaded-shift-op-parentheses") \ + doctest::detail::decomp_assert( \ + doctest::assertType::assert_type, __FILE__, __LINE__, #__VA_ARGS__, \ + doctest::detail::ExpressionDecomposer(doctest::assertType::assert_type) \ + << __VA_ARGS__) DOCTEST_CLANG_SUPPRESS_WARNING_POP + +#endif // DOCTEST_CONFIG_SUPER_FAST_ASSERTS + +#define DOCTEST_WARN(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_WARN, __VA_ARGS__) +#define DOCTEST_CHECK(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_CHECK, __VA_ARGS__) +#define DOCTEST_REQUIRE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_REQUIRE, __VA_ARGS__) +#define DOCTEST_WARN_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_WARN_FALSE, __VA_ARGS__) +#define DOCTEST_CHECK_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_CHECK_FALSE, __VA_ARGS__) +#define DOCTEST_REQUIRE_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_REQUIRE_FALSE, __VA_ARGS__) + +// clang-format off +#define DOCTEST_WARN_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(DT_WARN, cond); } while((void)0, 0) +#define DOCTEST_CHECK_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(DT_CHECK, cond); } while((void)0, 0) +#define DOCTEST_REQUIRE_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(DT_REQUIRE, cond); } while((void)0, 0) +#define DOCTEST_WARN_FALSE_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(DT_WARN_FALSE, cond); } while((void)0, 0) +#define DOCTEST_CHECK_FALSE_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(DT_CHECK_FALSE, cond); } while((void)0, 0) +#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(DT_REQUIRE_FALSE, cond); } while((void)0, 0) +// clang-format on + +#define DOCTEST_ASSERT_THROWS(expr, assert_type) DOCTEST_ASSERT_THROWS_WITH(expr, assert_type, "") + +#define DOCTEST_ASSERT_THROWS_AS(expr, assert_type, ...) \ + do { \ + if(!doctest::getContextOptions()->no_throw) { \ + doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + __LINE__, #expr, #__VA_ARGS__); \ + try { \ + expr; \ + } catch(const doctest::detail::remove_const< \ + doctest::detail::remove_reference<__VA_ARGS__>::type>::type&) { \ + _DOCTEST_RB.translateException(); \ + _DOCTEST_RB.m_threw_as = true; \ + } catch(...) { _DOCTEST_RB.translateException(); } \ + DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \ + } \ + } while((void)0, 0) + +#define DOCTEST_ASSERT_THROWS_WITH(expr, assert_type, ...) \ + do { \ + if(!doctest::getContextOptions()->no_throw) { \ + doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + __LINE__, #expr, __VA_ARGS__); \ + try { \ + expr; \ + } catch(...) { _DOCTEST_RB.translateException(); } \ + DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \ + } \ + } while((void)0, 0) + +#define DOCTEST_ASSERT_NOTHROW(expr, assert_type) \ + do { \ + doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + __LINE__, #expr); \ + try { \ + expr; \ + } catch(...) { _DOCTEST_RB.translateException(); } \ + DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \ + } while((void)0, 0) + +// clang-format off +#define DOCTEST_WARN_THROWS(expr) DOCTEST_ASSERT_THROWS(expr, DT_WARN_THROWS) +#define DOCTEST_CHECK_THROWS(expr) DOCTEST_ASSERT_THROWS(expr, DT_CHECK_THROWS) +#define DOCTEST_REQUIRE_THROWS(expr) DOCTEST_ASSERT_THROWS(expr, DT_REQUIRE_THROWS) + +#define DOCTEST_WARN_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_WARN_THROWS_AS, __VA_ARGS__) +#define DOCTEST_CHECK_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_CHECK_THROWS_AS, __VA_ARGS__) +#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_REQUIRE_THROWS_AS, __VA_ARGS__) + +#define DOCTEST_WARN_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, DT_WARN_THROWS_WITH, __VA_ARGS__) +#define DOCTEST_CHECK_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, DT_CHECK_THROWS_WITH, __VA_ARGS__) +#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, DT_REQUIRE_THROWS_WITH, __VA_ARGS__) + +#define DOCTEST_WARN_NOTHROW(expr) DOCTEST_ASSERT_NOTHROW(expr, DT_WARN_NOTHROW) +#define DOCTEST_CHECK_NOTHROW(expr) DOCTEST_ASSERT_NOTHROW(expr, DT_CHECK_NOTHROW) +#define DOCTEST_REQUIRE_NOTHROW(expr) DOCTEST_ASSERT_NOTHROW(expr, DT_REQUIRE_NOTHROW) + +#define DOCTEST_WARN_THROWS_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_WARN_THROWS(expr); } while((void)0, 0) +#define DOCTEST_CHECK_THROWS_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_CHECK_THROWS(expr); } while((void)0, 0) +#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_REQUIRE_THROWS(expr); } while((void)0, 0) +#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, msg) do { DOCTEST_INFO(msg); DOCTEST_WARN_THROWS_AS(expr, ex); } while((void)0, 0) +#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, msg) do { DOCTEST_INFO(msg); DOCTEST_CHECK_THROWS_AS(expr, ex); } while((void)0, 0) +#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, msg) do { DOCTEST_INFO(msg); DOCTEST_REQUIRE_THROWS_AS(expr, ex); } while((void)0, 0) +#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, ex, msg) do { DOCTEST_INFO(msg); DOCTEST_WARN_THROWS_WITH(expr, ex); } while((void)0, 0) +#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, ex, msg) do { DOCTEST_INFO(msg); DOCTEST_CHECK_THROWS_WITH(expr, ex); } while((void)0, 0) +#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, ex, msg) do { DOCTEST_INFO(msg); DOCTEST_REQUIRE_THROWS_WITH(expr, ex); } while((void)0, 0) +#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_WARN_NOTHROW(expr); } while((void)0, 0) +#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_CHECK_NOTHROW(expr); } while((void)0, 0) +#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_REQUIRE_NOTHROW(expr); } while((void)0, 0) +// clang-format on + +#ifndef DOCTEST_CONFIG_SUPER_FAST_ASSERTS + +#define DOCTEST_BINARY_ASSERT(assert_type, comp, ...) \ + do { \ + doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + __LINE__, #__VA_ARGS__); \ + DOCTEST_WRAP_IN_TRY( \ + _DOCTEST_RB.binary_assert( \ + __VA_ARGS__)) \ + DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \ + } while((void)0, 0) + +#define DOCTEST_UNARY_ASSERT(assert_type, ...) \ + do { \ + doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + __LINE__, #__VA_ARGS__); \ + DOCTEST_WRAP_IN_TRY(_DOCTEST_RB.unary_assert(__VA_ARGS__)) \ + DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \ + } while((void)0, 0) + +#else // DOCTEST_CONFIG_SUPER_FAST_ASSERTS + +#define DOCTEST_BINARY_ASSERT(assert_type, comparison, ...) \ + doctest::detail::binary_assert( \ + doctest::assertType::assert_type, __FILE__, __LINE__, #__VA_ARGS__, __VA_ARGS__) + +#define DOCTEST_UNARY_ASSERT(assert_type, ...) \ + doctest::detail::unary_assert(doctest::assertType::assert_type, __FILE__, __LINE__, \ + #__VA_ARGS__, __VA_ARGS__) + +#endif // DOCTEST_CONFIG_SUPER_FAST_ASSERTS + +#define DOCTEST_WARN_EQ(...) DOCTEST_BINARY_ASSERT(DT_WARN_EQ, eq, __VA_ARGS__) +#define DOCTEST_CHECK_EQ(...) DOCTEST_BINARY_ASSERT(DT_CHECK_EQ, eq, __VA_ARGS__) +#define DOCTEST_REQUIRE_EQ(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_EQ, eq, __VA_ARGS__) +#define DOCTEST_WARN_NE(...) DOCTEST_BINARY_ASSERT(DT_WARN_NE, ne, __VA_ARGS__) +#define DOCTEST_CHECK_NE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_NE, ne, __VA_ARGS__) +#define DOCTEST_REQUIRE_NE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_NE, ne, __VA_ARGS__) +#define DOCTEST_WARN_GT(...) DOCTEST_BINARY_ASSERT(DT_WARN_GT, gt, __VA_ARGS__) +#define DOCTEST_CHECK_GT(...) DOCTEST_BINARY_ASSERT(DT_CHECK_GT, gt, __VA_ARGS__) +#define DOCTEST_REQUIRE_GT(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_GT, gt, __VA_ARGS__) +#define DOCTEST_WARN_LT(...) DOCTEST_BINARY_ASSERT(DT_WARN_LT, lt, __VA_ARGS__) +#define DOCTEST_CHECK_LT(...) DOCTEST_BINARY_ASSERT(DT_CHECK_LT, lt, __VA_ARGS__) +#define DOCTEST_REQUIRE_LT(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_LT, lt, __VA_ARGS__) +#define DOCTEST_WARN_GE(...) DOCTEST_BINARY_ASSERT(DT_WARN_GE, ge, __VA_ARGS__) +#define DOCTEST_CHECK_GE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_GE, ge, __VA_ARGS__) +#define DOCTEST_REQUIRE_GE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_GE, ge, __VA_ARGS__) +#define DOCTEST_WARN_LE(...) DOCTEST_BINARY_ASSERT(DT_WARN_LE, le, __VA_ARGS__) +#define DOCTEST_CHECK_LE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_LE, le, __VA_ARGS__) +#define DOCTEST_REQUIRE_LE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_LE, le, __VA_ARGS__) + +#define DOCTEST_WARN_UNARY(...) DOCTEST_UNARY_ASSERT(DT_WARN_UNARY, __VA_ARGS__) +#define DOCTEST_CHECK_UNARY(...) DOCTEST_UNARY_ASSERT(DT_CHECK_UNARY, __VA_ARGS__) +#define DOCTEST_REQUIRE_UNARY(...) DOCTEST_UNARY_ASSERT(DT_REQUIRE_UNARY, __VA_ARGS__) +#define DOCTEST_WARN_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_WARN_UNARY_FALSE, __VA_ARGS__) +#define DOCTEST_CHECK_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_CHECK_UNARY_FALSE, __VA_ARGS__) +#define DOCTEST_REQUIRE_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_REQUIRE_UNARY_FALSE, __VA_ARGS__) + +#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS + +#undef DOCTEST_WARN_THROWS +#undef DOCTEST_CHECK_THROWS +#undef DOCTEST_REQUIRE_THROWS +#undef DOCTEST_WARN_THROWS_AS +#undef DOCTEST_CHECK_THROWS_AS +#undef DOCTEST_REQUIRE_THROWS_AS +#undef DOCTEST_WARN_THROWS_WITH +#undef DOCTEST_CHECK_THROWS_WITH +#undef DOCTEST_REQUIRE_THROWS_WITH +#undef DOCTEST_WARN_NOTHROW +#undef DOCTEST_CHECK_NOTHROW +#undef DOCTEST_REQUIRE_NOTHROW + +#undef DOCTEST_WARN_THROWS_MESSAGE +#undef DOCTEST_CHECK_THROWS_MESSAGE +#undef DOCTEST_REQUIRE_THROWS_MESSAGE +#undef DOCTEST_WARN_THROWS_AS_MESSAGE +#undef DOCTEST_CHECK_THROWS_AS_MESSAGE +#undef DOCTEST_REQUIRE_THROWS_AS_MESSAGE +#undef DOCTEST_WARN_THROWS_WITH_MESSAGE +#undef DOCTEST_CHECK_THROWS_WITH_MESSAGE +#undef DOCTEST_REQUIRE_THROWS_WITH_MESSAGE +#undef DOCTEST_WARN_NOTHROW_MESSAGE +#undef DOCTEST_CHECK_NOTHROW_MESSAGE +#undef DOCTEST_REQUIRE_NOTHROW_MESSAGE + +#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS + +#define DOCTEST_WARN_THROWS(expr) ((void)0) +#define DOCTEST_CHECK_THROWS(expr) ((void)0) +#define DOCTEST_REQUIRE_THROWS(expr) ((void)0) +#define DOCTEST_WARN_THROWS_AS(expr, ...) ((void)0) +#define DOCTEST_CHECK_THROWS_AS(expr, ...) ((void)0) +#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) ((void)0) +#define DOCTEST_WARN_THROWS_WITH(expr, ...) ((void)0) +#define DOCTEST_CHECK_THROWS_WITH(expr, ...) ((void)0) +#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) ((void)0) +#define DOCTEST_WARN_NOTHROW(expr) ((void)0) +#define DOCTEST_CHECK_NOTHROW(expr) ((void)0) +#define DOCTEST_REQUIRE_NOTHROW(expr) ((void)0) + +#define DOCTEST_WARN_THROWS_MESSAGE(expr, msg) ((void)0) +#define DOCTEST_CHECK_THROWS_MESSAGE(expr, msg) ((void)0) +#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, msg) ((void)0) +#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0) +#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0) +#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0) +#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, ex, msg) ((void)0) +#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, ex, msg) ((void)0) +#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, ex, msg) ((void)0) +#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, msg) ((void)0) +#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, msg) ((void)0) +#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, msg) ((void)0) + +#else // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS + +#undef DOCTEST_REQUIRE +#undef DOCTEST_REQUIRE_FALSE +#undef DOCTEST_REQUIRE_MESSAGE +#undef DOCTEST_REQUIRE_FALSE_MESSAGE +#undef DOCTEST_REQUIRE_EQ +#undef DOCTEST_REQUIRE_NE +#undef DOCTEST_REQUIRE_GT +#undef DOCTEST_REQUIRE_LT +#undef DOCTEST_REQUIRE_GE +#undef DOCTEST_REQUIRE_LE +#undef DOCTEST_REQUIRE_UNARY +#undef DOCTEST_REQUIRE_UNARY_FALSE + +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS + +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + +// ================================================================================================= +// == WHAT FOLLOWS IS VERSIONS OF THE MACROS THAT DO NOT DO ANY REGISTERING! == +// == THIS CAN BE ENABLED BY DEFINING DOCTEST_CONFIG_DISABLE GLOBALLY! == +// ================================================================================================= +#else // DOCTEST_CONFIG_DISABLE + +#define DOCTEST_IMPLEMENT_FIXTURE(der, base, func, name) \ + namespace { \ + template \ + struct der : public base \ + { void f(); }; \ + } \ + template \ + inline void der::f() + +#define DOCTEST_CREATE_AND_REGISTER_FUNCTION(f, name) \ + template \ + static inline void f() + +// for registering tests +#define DOCTEST_TEST_CASE(name) \ + DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), name) + +// for registering tests in classes +#define DOCTEST_TEST_CASE_CLASS(name) \ + DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), name) + +// for registering tests with a fixture +#define DOCTEST_TEST_CASE_FIXTURE(x, name) \ + DOCTEST_IMPLEMENT_FIXTURE(DOCTEST_ANONYMOUS(_DOCTEST_ANON_CLASS_), x, \ + DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), name) + +// for converting types to strings without the header and demangling +#define DOCTEST_TYPE_TO_STRING(...) typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) +#define DOCTEST_TYPE_TO_STRING_IMPL(...) + +// for typed tests +#define DOCTEST_TEST_CASE_TEMPLATE(name, type, ...) \ + template \ + inline void DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_)() + +#define DOCTEST_TEST_CASE_TEMPLATE_DEFINE(name, type, id) \ + template \ + inline void DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_)() + +#define DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, ...) \ + typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +#define DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, ...) \ + typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +// for subcases +#define DOCTEST_SUBCASE(name) + +// for a testsuite block +#define DOCTEST_TEST_SUITE(name) namespace + +// for starting a testsuite block +#define DOCTEST_TEST_SUITE_BEGIN(name) typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +// for ending a testsuite block +#define DOCTEST_TEST_SUITE_END typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +#define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR(signature) \ + template \ + static inline doctest::String DOCTEST_ANONYMOUS(_DOCTEST_ANON_TRANSLATOR_)(signature) + +#define DOCTEST_REGISTER_REPORTER(name, priority, reporter) + +#define DOCTEST_INFO(x) ((void)0) +#define DOCTEST_CAPTURE(x) ((void)0) +#define DOCTEST_ADD_MESSAGE_AT(file, line, x) ((void)0) +#define DOCTEST_ADD_FAIL_CHECK_AT(file, line, x) ((void)0) +#define DOCTEST_ADD_FAIL_AT(file, line, x) ((void)0) +#define DOCTEST_MESSAGE(x) ((void)0) +#define DOCTEST_FAIL_CHECK(x) ((void)0) +#define DOCTEST_FAIL(x) ((void)0) + +#define DOCTEST_WARN(...) ((void)0) +#define DOCTEST_CHECK(...) ((void)0) +#define DOCTEST_REQUIRE(...) ((void)0) +#define DOCTEST_WARN_FALSE(...) ((void)0) +#define DOCTEST_CHECK_FALSE(...) ((void)0) +#define DOCTEST_REQUIRE_FALSE(...) ((void)0) + +#define DOCTEST_WARN_MESSAGE(cond, msg) ((void)0) +#define DOCTEST_CHECK_MESSAGE(cond, msg) ((void)0) +#define DOCTEST_REQUIRE_MESSAGE(cond, msg) ((void)0) +#define DOCTEST_WARN_FALSE_MESSAGE(cond, msg) ((void)0) +#define DOCTEST_CHECK_FALSE_MESSAGE(cond, msg) ((void)0) +#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, msg) ((void)0) + +#define DOCTEST_WARN_THROWS(expr) ((void)0) +#define DOCTEST_CHECK_THROWS(expr) ((void)0) +#define DOCTEST_REQUIRE_THROWS(expr) ((void)0) +#define DOCTEST_WARN_THROWS_AS(expr, ...) ((void)0) +#define DOCTEST_CHECK_THROWS_AS(expr, ...) ((void)0) +#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) ((void)0) +#define DOCTEST_WARN_THROWS_WITH(expr, ...) ((void)0) +#define DOCTEST_CHECK_THROWS_WITH(expr, ...) ((void)0) +#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) ((void)0) +#define DOCTEST_WARN_NOTHROW(expr) ((void)0) +#define DOCTEST_CHECK_NOTHROW(expr) ((void)0) +#define DOCTEST_REQUIRE_NOTHROW(expr) ((void)0) + +#define DOCTEST_WARN_THROWS_MESSAGE(expr, msg) ((void)0) +#define DOCTEST_CHECK_THROWS_MESSAGE(expr, msg) ((void)0) +#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, msg) ((void)0) +#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0) +#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0) +#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0) +#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, ex, msg) ((void)0) +#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, ex, msg) ((void)0) +#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, ex, msg) ((void)0) +#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, msg) ((void)0) +#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, msg) ((void)0) +#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, msg) ((void)0) + +#define DOCTEST_WARN_EQ(...) ((void)0) +#define DOCTEST_CHECK_EQ(...) ((void)0) +#define DOCTEST_REQUIRE_EQ(...) ((void)0) +#define DOCTEST_WARN_NE(...) ((void)0) +#define DOCTEST_CHECK_NE(...) ((void)0) +#define DOCTEST_REQUIRE_NE(...) ((void)0) +#define DOCTEST_WARN_GT(...) ((void)0) +#define DOCTEST_CHECK_GT(...) ((void)0) +#define DOCTEST_REQUIRE_GT(...) ((void)0) +#define DOCTEST_WARN_LT(...) ((void)0) +#define DOCTEST_CHECK_LT(...) ((void)0) +#define DOCTEST_REQUIRE_LT(...) ((void)0) +#define DOCTEST_WARN_GE(...) ((void)0) +#define DOCTEST_CHECK_GE(...) ((void)0) +#define DOCTEST_REQUIRE_GE(...) ((void)0) +#define DOCTEST_WARN_LE(...) ((void)0) +#define DOCTEST_CHECK_LE(...) ((void)0) +#define DOCTEST_REQUIRE_LE(...) ((void)0) + +#define DOCTEST_WARN_UNARY(...) ((void)0) +#define DOCTEST_CHECK_UNARY(...) ((void)0) +#define DOCTEST_REQUIRE_UNARY(...) ((void)0) +#define DOCTEST_WARN_UNARY_FALSE(...) ((void)0) +#define DOCTEST_CHECK_UNARY_FALSE(...) ((void)0) +#define DOCTEST_REQUIRE_UNARY_FALSE(...) ((void)0) + +#endif // DOCTEST_CONFIG_DISABLE + +// clang-format off +// KEPT FOR BACKWARDS COMPATIBILITY - FORWARDING TO THE RIGHT MACROS +#define DOCTEST_FAST_WARN_EQ DOCTEST_WARN_EQ +#define DOCTEST_FAST_CHECK_EQ DOCTEST_CHECK_EQ +#define DOCTEST_FAST_REQUIRE_EQ DOCTEST_REQUIRE_EQ +#define DOCTEST_FAST_WARN_NE DOCTEST_WARN_NE +#define DOCTEST_FAST_CHECK_NE DOCTEST_CHECK_NE +#define DOCTEST_FAST_REQUIRE_NE DOCTEST_REQUIRE_NE +#define DOCTEST_FAST_WARN_GT DOCTEST_WARN_GT +#define DOCTEST_FAST_CHECK_GT DOCTEST_CHECK_GT +#define DOCTEST_FAST_REQUIRE_GT DOCTEST_REQUIRE_GT +#define DOCTEST_FAST_WARN_LT DOCTEST_WARN_LT +#define DOCTEST_FAST_CHECK_LT DOCTEST_CHECK_LT +#define DOCTEST_FAST_REQUIRE_LT DOCTEST_REQUIRE_LT +#define DOCTEST_FAST_WARN_GE DOCTEST_WARN_GE +#define DOCTEST_FAST_CHECK_GE DOCTEST_CHECK_GE +#define DOCTEST_FAST_REQUIRE_GE DOCTEST_REQUIRE_GE +#define DOCTEST_FAST_WARN_LE DOCTEST_WARN_LE +#define DOCTEST_FAST_CHECK_LE DOCTEST_CHECK_LE +#define DOCTEST_FAST_REQUIRE_LE DOCTEST_REQUIRE_LE + +#define DOCTEST_FAST_WARN_UNARY DOCTEST_WARN_UNARY +#define DOCTEST_FAST_CHECK_UNARY DOCTEST_CHECK_UNARY +#define DOCTEST_FAST_REQUIRE_UNARY DOCTEST_REQUIRE_UNARY +#define DOCTEST_FAST_WARN_UNARY_FALSE DOCTEST_WARN_UNARY_FALSE +#define DOCTEST_FAST_CHECK_UNARY_FALSE DOCTEST_CHECK_UNARY_FALSE +#define DOCTEST_FAST_REQUIRE_UNARY_FALSE DOCTEST_REQUIRE_UNARY_FALSE + +#define DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE DOCTEST_TEST_CASE_TEMPLATE_INVOKE +// clang-format on + +// BDD style macros +// clang-format off +#define DOCTEST_SCENARIO(name) DOCTEST_TEST_CASE(" Scenario: " name) +#define DOCTEST_SCENARIO_CLASS(name) DOCTEST_TEST_CASE_CLASS(" Scenario: " name) +#define DOCTEST_SCENARIO_TEMPLATE(name, T, ...) DOCTEST_TEST_CASE_TEMPLATE(" Scenario: " name, T, __VA_ARGS__) +#define DOCTEST_SCENARIO_TEMPLATE_DEFINE(name, T, id) DOCTEST_TEST_CASE_TEMPLATE_DEFINE(" Scenario: " name, T, id) + +#define DOCTEST_GIVEN(name) SUBCASE(" Given: " name) +#define DOCTEST_WHEN(name) SUBCASE(" When: " name) +#define DOCTEST_AND_WHEN(name) SUBCASE("And when: " name) +#define DOCTEST_THEN(name) SUBCASE(" Then: " name) +#define DOCTEST_AND_THEN(name) SUBCASE(" And: " name) +// clang-format on + +// == SHORT VERSIONS OF THE MACROS +#if !defined(DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES) + +#define TEST_CASE DOCTEST_TEST_CASE +#define TEST_CASE_CLASS DOCTEST_TEST_CASE_CLASS +#define TEST_CASE_FIXTURE DOCTEST_TEST_CASE_FIXTURE +#define TYPE_TO_STRING DOCTEST_TYPE_TO_STRING +#define TEST_CASE_TEMPLATE DOCTEST_TEST_CASE_TEMPLATE +#define TEST_CASE_TEMPLATE_DEFINE DOCTEST_TEST_CASE_TEMPLATE_DEFINE +#define TEST_CASE_TEMPLATE_INVOKE DOCTEST_TEST_CASE_TEMPLATE_INVOKE +#define TEST_CASE_TEMPLATE_APPLY DOCTEST_TEST_CASE_TEMPLATE_APPLY +#define SUBCASE DOCTEST_SUBCASE +#define TEST_SUITE DOCTEST_TEST_SUITE +#define TEST_SUITE_BEGIN DOCTEST_TEST_SUITE_BEGIN +#define TEST_SUITE_END DOCTEST_TEST_SUITE_END +#define REGISTER_EXCEPTION_TRANSLATOR DOCTEST_REGISTER_EXCEPTION_TRANSLATOR +#define REGISTER_REPORTER DOCTEST_REGISTER_REPORTER +#define INFO DOCTEST_INFO +#define CAPTURE DOCTEST_CAPTURE +#define ADD_MESSAGE_AT DOCTEST_ADD_MESSAGE_AT +#define ADD_FAIL_CHECK_AT DOCTEST_ADD_FAIL_CHECK_AT +#define ADD_FAIL_AT DOCTEST_ADD_FAIL_AT +#define MESSAGE DOCTEST_MESSAGE +#define FAIL_CHECK DOCTEST_FAIL_CHECK +#define FAIL DOCTEST_FAIL +#define TO_LVALUE DOCTEST_TO_LVALUE + +#define WARN DOCTEST_WARN +#define WARN_FALSE DOCTEST_WARN_FALSE +#define WARN_THROWS DOCTEST_WARN_THROWS +#define WARN_THROWS_AS DOCTEST_WARN_THROWS_AS +#define WARN_THROWS_WITH DOCTEST_WARN_THROWS_WITH +#define WARN_NOTHROW DOCTEST_WARN_NOTHROW +#define CHECK DOCTEST_CHECK +#define CHECK_FALSE DOCTEST_CHECK_FALSE +#define CHECK_THROWS DOCTEST_CHECK_THROWS +#define CHECK_THROWS_AS DOCTEST_CHECK_THROWS_AS +#define CHECK_THROWS_WITH DOCTEST_CHECK_THROWS_WITH +#define CHECK_NOTHROW DOCTEST_CHECK_NOTHROW +#define REQUIRE DOCTEST_REQUIRE +#define REQUIRE_FALSE DOCTEST_REQUIRE_FALSE +#define REQUIRE_THROWS DOCTEST_REQUIRE_THROWS +#define REQUIRE_THROWS_AS DOCTEST_REQUIRE_THROWS_AS +#define REQUIRE_THROWS_WITH DOCTEST_REQUIRE_THROWS_WITH +#define REQUIRE_NOTHROW DOCTEST_REQUIRE_NOTHROW + +#define WARN_MESSAGE DOCTEST_WARN_MESSAGE +#define WARN_FALSE_MESSAGE DOCTEST_WARN_FALSE_MESSAGE +#define WARN_THROWS_MESSAGE DOCTEST_WARN_THROWS_MESSAGE +#define WARN_THROWS_AS_MESSAGE DOCTEST_WARN_THROWS_AS_MESSAGE +#define WARN_THROWS_WITH_MESSAGE DOCTEST_WARN_THROWS_WITH_MESSAGE +#define WARN_NOTHROW_MESSAGE DOCTEST_WARN_NOTHROW_MESSAGE +#define CHECK_MESSAGE DOCTEST_CHECK_MESSAGE +#define CHECK_FALSE_MESSAGE DOCTEST_CHECK_FALSE_MESSAGE +#define CHECK_THROWS_MESSAGE DOCTEST_CHECK_THROWS_MESSAGE +#define CHECK_THROWS_AS_MESSAGE DOCTEST_CHECK_THROWS_AS_MESSAGE +#define CHECK_THROWS_WITH_MESSAGE DOCTEST_CHECK_THROWS_WITH_MESSAGE +#define CHECK_NOTHROW_MESSAGE DOCTEST_CHECK_NOTHROW_MESSAGE +#define REQUIRE_MESSAGE DOCTEST_REQUIRE_MESSAGE +#define REQUIRE_FALSE_MESSAGE DOCTEST_REQUIRE_FALSE_MESSAGE +#define REQUIRE_THROWS_MESSAGE DOCTEST_REQUIRE_THROWS_MESSAGE +#define REQUIRE_THROWS_AS_MESSAGE DOCTEST_REQUIRE_THROWS_AS_MESSAGE +#define REQUIRE_THROWS_WITH_MESSAGE DOCTEST_REQUIRE_THROWS_WITH_MESSAGE +#define REQUIRE_NOTHROW_MESSAGE DOCTEST_REQUIRE_NOTHROW_MESSAGE + +#define SCENARIO DOCTEST_SCENARIO +#define SCENARIO_CLASS DOCTEST_SCENARIO_CLASS +#define SCENARIO_TEMPLATE DOCTEST_SCENARIO_TEMPLATE +#define SCENARIO_TEMPLATE_DEFINE DOCTEST_SCENARIO_TEMPLATE_DEFINE +#define GIVEN DOCTEST_GIVEN +#define WHEN DOCTEST_WHEN +#define AND_WHEN DOCTEST_AND_WHEN +#define THEN DOCTEST_THEN +#define AND_THEN DOCTEST_AND_THEN + +#define WARN_EQ DOCTEST_WARN_EQ +#define CHECK_EQ DOCTEST_CHECK_EQ +#define REQUIRE_EQ DOCTEST_REQUIRE_EQ +#define WARN_NE DOCTEST_WARN_NE +#define CHECK_NE DOCTEST_CHECK_NE +#define REQUIRE_NE DOCTEST_REQUIRE_NE +#define WARN_GT DOCTEST_WARN_GT +#define CHECK_GT DOCTEST_CHECK_GT +#define REQUIRE_GT DOCTEST_REQUIRE_GT +#define WARN_LT DOCTEST_WARN_LT +#define CHECK_LT DOCTEST_CHECK_LT +#define REQUIRE_LT DOCTEST_REQUIRE_LT +#define WARN_GE DOCTEST_WARN_GE +#define CHECK_GE DOCTEST_CHECK_GE +#define REQUIRE_GE DOCTEST_REQUIRE_GE +#define WARN_LE DOCTEST_WARN_LE +#define CHECK_LE DOCTEST_CHECK_LE +#define REQUIRE_LE DOCTEST_REQUIRE_LE +#define WARN_UNARY DOCTEST_WARN_UNARY +#define CHECK_UNARY DOCTEST_CHECK_UNARY +#define REQUIRE_UNARY DOCTEST_REQUIRE_UNARY +#define WARN_UNARY_FALSE DOCTEST_WARN_UNARY_FALSE +#define CHECK_UNARY_FALSE DOCTEST_CHECK_UNARY_FALSE +#define REQUIRE_UNARY_FALSE DOCTEST_REQUIRE_UNARY_FALSE + +// KEPT FOR BACKWARDS COMPATIBILITY +#define FAST_WARN_EQ DOCTEST_FAST_WARN_EQ +#define FAST_CHECK_EQ DOCTEST_FAST_CHECK_EQ +#define FAST_REQUIRE_EQ DOCTEST_FAST_REQUIRE_EQ +#define FAST_WARN_NE DOCTEST_FAST_WARN_NE +#define FAST_CHECK_NE DOCTEST_FAST_CHECK_NE +#define FAST_REQUIRE_NE DOCTEST_FAST_REQUIRE_NE +#define FAST_WARN_GT DOCTEST_FAST_WARN_GT +#define FAST_CHECK_GT DOCTEST_FAST_CHECK_GT +#define FAST_REQUIRE_GT DOCTEST_FAST_REQUIRE_GT +#define FAST_WARN_LT DOCTEST_FAST_WARN_LT +#define FAST_CHECK_LT DOCTEST_FAST_CHECK_LT +#define FAST_REQUIRE_LT DOCTEST_FAST_REQUIRE_LT +#define FAST_WARN_GE DOCTEST_FAST_WARN_GE +#define FAST_CHECK_GE DOCTEST_FAST_CHECK_GE +#define FAST_REQUIRE_GE DOCTEST_FAST_REQUIRE_GE +#define FAST_WARN_LE DOCTEST_FAST_WARN_LE +#define FAST_CHECK_LE DOCTEST_FAST_CHECK_LE +#define FAST_REQUIRE_LE DOCTEST_FAST_REQUIRE_LE + +#define FAST_WARN_UNARY DOCTEST_FAST_WARN_UNARY +#define FAST_CHECK_UNARY DOCTEST_FAST_CHECK_UNARY +#define FAST_REQUIRE_UNARY DOCTEST_FAST_REQUIRE_UNARY +#define FAST_WARN_UNARY_FALSE DOCTEST_FAST_WARN_UNARY_FALSE +#define FAST_CHECK_UNARY_FALSE DOCTEST_FAST_CHECK_UNARY_FALSE +#define FAST_REQUIRE_UNARY_FALSE DOCTEST_FAST_REQUIRE_UNARY_FALSE + +#define TEST_CASE_TEMPLATE_INSTANTIATE DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE + +#endif // DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES + +#if !defined(DOCTEST_CONFIG_DISABLE) + +// this is here to clear the 'current test suite' for the current translation unit - at the top +DOCTEST_TEST_SUITE_END(); + +// add stringification for primitive/fundamental types +namespace doctest { namespace detail { + DOCTEST_TYPE_TO_STRING_IMPL(bool) + DOCTEST_TYPE_TO_STRING_IMPL(float) + DOCTEST_TYPE_TO_STRING_IMPL(double) + DOCTEST_TYPE_TO_STRING_IMPL(long double) + DOCTEST_TYPE_TO_STRING_IMPL(char) + DOCTEST_TYPE_TO_STRING_IMPL(signed char) + DOCTEST_TYPE_TO_STRING_IMPL(unsigned char) +#if !DOCTEST_MSVC || defined(_NATIVE_WCHAR_T_DEFINED) + DOCTEST_TYPE_TO_STRING_IMPL(wchar_t) +#endif // not MSVC or wchar_t support enabled + DOCTEST_TYPE_TO_STRING_IMPL(short int) + DOCTEST_TYPE_TO_STRING_IMPL(unsigned short int) + DOCTEST_TYPE_TO_STRING_IMPL(int) + DOCTEST_TYPE_TO_STRING_IMPL(unsigned int) + DOCTEST_TYPE_TO_STRING_IMPL(long int) + DOCTEST_TYPE_TO_STRING_IMPL(unsigned long int) + DOCTEST_TYPE_TO_STRING_IMPL(long long int) + DOCTEST_TYPE_TO_STRING_IMPL(unsigned long long int) +}} // namespace doctest::detail + +#endif // DOCTEST_CONFIG_DISABLE + +DOCTEST_CLANG_SUPPRESS_WARNING_POP +DOCTEST_MSVC_SUPPRESS_WARNING_POP +DOCTEST_GCC_SUPPRESS_WARNING_POP + +#endif // DOCTEST_LIBRARY_INCLUDED + +#ifndef DOCTEST_SINGLE_HEADER +#define DOCTEST_SINGLE_HEADER +#endif // DOCTEST_SINGLE_HEADER + +#if defined(DOCTEST_CONFIG_IMPLEMENT) || !defined(DOCTEST_SINGLE_HEADER) +#ifndef DOCTEST_LIBRARY_IMPLEMENTATION +#define DOCTEST_LIBRARY_IMPLEMENTATION + +#ifndef DOCTEST_SINGLE_HEADER +#include "doctest_fwd.h" +#endif // DOCTEST_SINGLE_HEADER + +DOCTEST_CLANG_SUPPRESS_WARNING_PUSH +DOCTEST_CLANG_SUPPRESS_WARNING("-Wunknown-pragmas") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wpadded") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wweak-vtables") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wglobal-constructors") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-prototypes") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wsign-conversion") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wshorten-64-to-32") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-variable-declarations") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wswitch") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wswitch-enum") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wcovered-switch-default") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-noreturn") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-local-typedef") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wdisabled-macro-expansion") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-braces") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-field-initializers") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat-pedantic") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-member-function") + +DOCTEST_GCC_SUPPRESS_WARNING_PUSH +DOCTEST_GCC_SUPPRESS_WARNING("-Wunknown-pragmas") +DOCTEST_GCC_SUPPRESS_WARNING("-Wpragmas") +DOCTEST_GCC_SUPPRESS_WARNING("-Wconversion") +DOCTEST_GCC_SUPPRESS_WARNING("-Weffc++") +DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-conversion") +DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-overflow") +DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-aliasing") +DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-field-initializers") +DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-braces") +DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-declarations") +DOCTEST_GCC_SUPPRESS_WARNING("-Winline") +DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch") +DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch-enum") +DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch-default") +DOCTEST_GCC_SUPPRESS_WARNING("-Wunsafe-loop-optimizations") +DOCTEST_GCC_SUPPRESS_WARNING("-Wold-style-cast") +DOCTEST_GCC_SUPPRESS_WARNING("-Wunused-local-typedefs") +DOCTEST_GCC_SUPPRESS_WARNING("-Wuseless-cast") +DOCTEST_GCC_SUPPRESS_WARNING("-Wunused-function") + +DOCTEST_MSVC_SUPPRESS_WARNING_PUSH +DOCTEST_MSVC_SUPPRESS_WARNING(4616) // invalid compiler warning +DOCTEST_MSVC_SUPPRESS_WARNING(4619) // invalid compiler warning +DOCTEST_MSVC_SUPPRESS_WARNING(4996) // The compiler encountered a deprecated declaration +DOCTEST_MSVC_SUPPRESS_WARNING(4267) // 'var' : conversion from 'x' to 'y', possible loss of data +DOCTEST_MSVC_SUPPRESS_WARNING(4706) // assignment within conditional expression +DOCTEST_MSVC_SUPPRESS_WARNING(4512) // 'class' : assignment operator could not be generated +DOCTEST_MSVC_SUPPRESS_WARNING(4127) // conditional expression is constant +DOCTEST_MSVC_SUPPRESS_WARNING(4530) // C++ exception handler used, but unwind semantics not enabled +DOCTEST_MSVC_SUPPRESS_WARNING(4577) // 'noexcept' used with no exception handling mode specified +DOCTEST_MSVC_SUPPRESS_WARNING(4774) // format string expected in argument is not a string literal +DOCTEST_MSVC_SUPPRESS_WARNING(4365) // conversion from 'int' to 'unsigned', signed/unsigned mismatch +DOCTEST_MSVC_SUPPRESS_WARNING(4820) // padding in structs +DOCTEST_MSVC_SUPPRESS_WARNING(4640) // construction of local static object is not thread-safe +DOCTEST_MSVC_SUPPRESS_WARNING(5039) // pointer to potentially throwing function passed to extern C +DOCTEST_MSVC_SUPPRESS_WARNING(5045) // Spectre mitigation stuff +DOCTEST_MSVC_SUPPRESS_WARNING(4626) // assignment operator was implicitly defined as deleted +DOCTEST_MSVC_SUPPRESS_WARNING(5027) // move assignment operator was implicitly defined as deleted +DOCTEST_MSVC_SUPPRESS_WARNING(5026) // move constructor was implicitly defined as deleted +DOCTEST_MSVC_SUPPRESS_WARNING(4625) // copy constructor was implicitly defined as deleted +DOCTEST_MSVC_SUPPRESS_WARNING(4800) // forcing value to bool 'true' or 'false' (performance warning) +// static analysis +DOCTEST_MSVC_SUPPRESS_WARNING(26439) // This kind of function may not throw. Declare it 'noexcept' +DOCTEST_MSVC_SUPPRESS_WARNING(26495) // Always initialize a member variable +DOCTEST_MSVC_SUPPRESS_WARNING(26451) // Arithmetic overflow ... +DOCTEST_MSVC_SUPPRESS_WARNING(26444) // Avoid unnamed objects with custom construction and dtor... + +DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN + +// required includes - will go only in one translation unit! +#include +#include +#include +// borland (Embarcadero) compiler requires math.h and not cmath - https://github.com/onqtam/doctest/pull/37 +#ifdef __BORLANDC__ +#include +#endif // __BORLANDC__ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef DOCTEST_CONFIG_POSIX_SIGNALS +#include +#endif // DOCTEST_CONFIG_POSIX_SIGNALS +#include +#include +#include + +#ifdef DOCTEST_PLATFORM_MAC +#include +#include +#include +#endif // DOCTEST_PLATFORM_MAC + +#ifdef DOCTEST_PLATFORM_WINDOWS + +// defines for a leaner windows.h +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif // WIN32_LEAN_AND_MEAN +#ifndef VC_EXTRA_LEAN +#define VC_EXTRA_LEAN +#endif // VC_EXTRA_LEAN +#ifndef NOMINMAX +#define NOMINMAX +#endif // NOMINMAX + +// not sure what AfxWin.h is for - here I do what Catch does +#ifdef __AFXDLL +#include +#else +#include +#endif +#include + +#else // DOCTEST_PLATFORM_WINDOWS + +#include +#include + +#endif // DOCTEST_PLATFORM_WINDOWS + +DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END + +// counts the number of elements in a C array +#define DOCTEST_COUNTOF(x) (sizeof(x) / sizeof(x[0])) + +#ifdef DOCTEST_CONFIG_DISABLE +#define DOCTEST_BRANCH_ON_DISABLED(if_disabled, if_not_disabled) if_disabled +#else // DOCTEST_CONFIG_DISABLE +#define DOCTEST_BRANCH_ON_DISABLED(if_disabled, if_not_disabled) if_not_disabled +#endif // DOCTEST_CONFIG_DISABLE + +#ifndef DOCTEST_CONFIG_OPTIONS_PREFIX +#define DOCTEST_CONFIG_OPTIONS_PREFIX "dt-" +#endif + +#ifndef DOCTEST_THREAD_LOCAL +#define DOCTEST_THREAD_LOCAL thread_local +#endif + +#ifdef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS +#define DOCTEST_OPTIONS_PREFIX_DISPLAY DOCTEST_CONFIG_OPTIONS_PREFIX +#else +#define DOCTEST_OPTIONS_PREFIX_DISPLAY "" +#endif + +namespace doctest { + +bool is_running_in_test = false; + +namespace { + using namespace detail; + // case insensitive strcmp + int stricmp(const char* a, const char* b) { + for(;; a++, b++) { + const int d = tolower(*a) - tolower(*b); + if(d != 0 || !*a) + return d; + } + } + + template + String fpToString(T value, int precision) { + std::ostringstream oss; + oss << std::setprecision(precision) << std::fixed << value; + std::string d = oss.str(); + size_t i = d.find_last_not_of('0'); + if(i != std::string::npos && i != d.size() - 1) { + if(d[i] == '.') + i++; + d = d.substr(0, i + 1); + } + return d.c_str(); + } + + struct Endianness + { + enum Arch + { + Big, + Little + }; + + static Arch which() { + int x = 1; + // casting any data pointer to char* is allowed + auto ptr = reinterpret_cast(&x); + if(*ptr) + return Little; + return Big; + } + }; +} // namespace + +namespace detail { + void my_memcpy(void* dest, const void* src, unsigned num) { memcpy(dest, src, num); } + + String rawMemoryToString(const void* object, unsigned size) { + // Reverse order for little endian architectures + int i = 0, end = static_cast(size), inc = 1; + if(Endianness::which() == Endianness::Little) { + i = end - 1; + end = inc = -1; + } + + unsigned const char* bytes = static_cast(object); + std::ostringstream oss; + oss << "0x" << std::setfill('0') << std::hex; + for(; i != end; i += inc) + oss << std::setw(2) << static_cast(bytes[i]); + return oss.str().c_str(); + } + + DOCTEST_THREAD_LOCAL std::ostringstream g_oss; + + std::ostream* getTlsOss() { + g_oss.clear(); // there shouldn't be anything worth clearing in the flags + g_oss.str(""); // the slow way of resetting a string stream + //g_oss.seekp(0); // optimal reset - as seen here: https://stackoverflow.com/a/624291/3162383 + return &g_oss; + } + + String getTlsOssResult() { + //g_oss << std::ends; // needed - as shown here: https://stackoverflow.com/a/624291/3162383 + return g_oss.str().c_str(); + } + +#ifndef DOCTEST_CONFIG_DISABLE + + typedef uint64_t UInt64; + +#ifdef DOCTEST_CONFIG_GETCURRENTTICKS + UInt64 getCurrentTicks() { return DOCTEST_CONFIG_GETCURRENTTICKS(); } +#elif defined(DOCTEST_PLATFORM_WINDOWS) + UInt64 getCurrentTicks() { + static UInt64 hz = 0, hzo = 0; + if(!hz) { + QueryPerformanceFrequency(reinterpret_cast(&hz)); + QueryPerformanceCounter(reinterpret_cast(&hzo)); + } + UInt64 t; + QueryPerformanceCounter(reinterpret_cast(&t)); + return ((t - hzo) * 1000000) / hz; + } +#else // DOCTEST_PLATFORM_WINDOWS + UInt64 getCurrentTicks() { + timeval t; + gettimeofday(&t, nullptr); + return static_cast(t.tv_sec) * 1000000 + static_cast(t.tv_usec); + } +#endif // DOCTEST_PLATFORM_WINDOWS + + struct Timer + { + void start() { m_ticks = getCurrentTicks(); } + unsigned int getElapsedMicroseconds() const { + return static_cast(getCurrentTicks() - m_ticks); + } + //unsigned int getElapsedMilliseconds() const { + // return static_cast(getElapsedMicroseconds() / 1000); + //} + double getElapsedSeconds() const { return getElapsedMicroseconds() / 1000000.0; } + + private: + UInt64 m_ticks = 0; + }; + + // this holds both parameters from the command line and runtime data for tests + struct ContextState : ContextOptions, TestRunStats, CurrentTestCaseStats + { + std::atomic numAssertsCurrentTest_atomic; + std::atomic numAssertsFailedCurrentTest_atomic; + + std::vector> filters = decltype(filters)(9); // 9 different filters + + std::vector reporters_currently_used; + + const TestCase* currentTest = nullptr; + + assert_handler ah = nullptr; + + Timer timer; + + std::vector stringifiedContexts; // logging from INFO() due to an exception + + // stuff for subcases + std::set subcasesPassed; + std::set subcasesEnteredLevels; + int subcasesCurrentLevel; + bool should_reenter; + + void resetRunData() { + numTestCases = 0; + numTestCasesPassingFilters = 0; + numTestSuitesPassingFilters = 0; + numTestCasesFailed = 0; + numAsserts = 0; + numAssertsFailed = 0; + numAssertsCurrentTest = 0; + numAssertsFailedCurrentTest = 0; + } + + void finalizeTestCaseData() { + seconds = timer.getElapsedSeconds(); + + // update the non-atomic counters + numAsserts += numAssertsCurrentTest_atomic; + numAssertsFailed += numAssertsFailedCurrentTest_atomic; + numAssertsCurrentTest = numAssertsCurrentTest_atomic; + numAssertsFailedCurrentTest = numAssertsFailedCurrentTest_atomic; + + if(numAssertsFailedCurrentTest) + failure_flags |= TestCaseFailureReason::AssertFailure; + + if(Approx(currentTest->m_timeout).epsilon(DBL_EPSILON) != 0 && + Approx(seconds).epsilon(DBL_EPSILON) > currentTest->m_timeout) + failure_flags |= TestCaseFailureReason::Timeout; + + if(currentTest->m_should_fail) { + if(failure_flags) { + failure_flags |= TestCaseFailureReason::ShouldHaveFailedAndDid; + } else { + failure_flags |= TestCaseFailureReason::ShouldHaveFailedButDidnt; + } + } else if(failure_flags && currentTest->m_may_fail) { + failure_flags |= TestCaseFailureReason::CouldHaveFailedAndDid; + } else if(currentTest->m_expected_failures > 0) { + if(numAssertsFailedCurrentTest == currentTest->m_expected_failures) { + failure_flags |= TestCaseFailureReason::FailedExactlyNumTimes; + } else { + failure_flags |= TestCaseFailureReason::DidntFailExactlyNumTimes; + } + } + + bool ok_to_fail = (TestCaseFailureReason::ShouldHaveFailedAndDid & failure_flags) || + (TestCaseFailureReason::CouldHaveFailedAndDid & failure_flags) || + (TestCaseFailureReason::FailedExactlyNumTimes & failure_flags); + + // if any subcase has failed - the whole test case has failed + if(failure_flags && !ok_to_fail) + numTestCasesFailed++; + } + }; + + ContextState* g_cs = nullptr; + + // used to avoid locks for the debug output + // TODO: figure out if this is indeed necessary/correct - seems like either there still + // could be a race or that there wouldn't be a race even if using the context directly + DOCTEST_THREAD_LOCAL bool g_no_colors; + +#endif // DOCTEST_CONFIG_DISABLE +} // namespace detail + +void String::setOnHeap() { *reinterpret_cast(&buf[last]) = 128; } +void String::setLast(unsigned in) { buf[last] = char(in); } + +void String::copy(const String& other) { + if(other.isOnStack()) { + memcpy(buf, other.buf, len); + } else { + setOnHeap(); + data.size = other.data.size; + data.capacity = data.size + 1; + data.ptr = new char[data.capacity]; + memcpy(data.ptr, other.data.ptr, data.size + 1); + } +} + +String::String() { + buf[0] = '\0'; + setLast(); +} + +String::~String() { + if(!isOnStack()) + delete[] data.ptr; +} + +String::String(const char* in) + : String(in, strlen(in)) {} + +String::String(const char* in, unsigned in_size) { + if(in_size <= last) { + memcpy(buf, in, in_size + 1); + setLast(last - in_size); + } else { + setOnHeap(); + data.size = in_size; + data.capacity = data.size + 1; + data.ptr = new char[data.capacity]; + memcpy(data.ptr, in, in_size + 1); + } +} + +String::String(const String& other) { copy(other); } + +String& String::operator=(const String& other) { + if(this != &other) { + if(!isOnStack()) + delete[] data.ptr; + + copy(other); + } + + return *this; +} + +String& String::operator+=(const String& other) { + const unsigned my_old_size = size(); + const unsigned other_size = other.size(); + const unsigned total_size = my_old_size + other_size; + if(isOnStack()) { + if(total_size < len) { + // append to the current stack space + memcpy(buf + my_old_size, other.c_str(), other_size + 1); + setLast(last - total_size); + } else { + // alloc new chunk + char* temp = new char[total_size + 1]; + // copy current data to new location before writing in the union + memcpy(temp, buf, my_old_size); // skip the +1 ('\0') for speed + // update data in union + setOnHeap(); + data.size = total_size; + data.capacity = data.size + 1; + data.ptr = temp; + // transfer the rest of the data + memcpy(data.ptr + my_old_size, other.c_str(), other_size + 1); + } + } else { + if(data.capacity > total_size) { + // append to the current heap block + data.size = total_size; + memcpy(data.ptr + my_old_size, other.c_str(), other_size + 1); + } else { + // resize + data.capacity *= 2; + if(data.capacity <= total_size) + data.capacity = total_size + 1; + // alloc new chunk + char* temp = new char[data.capacity]; + // copy current data to new location before releasing it + memcpy(temp, data.ptr, my_old_size); // skip the +1 ('\0') for speed + // release old chunk + delete[] data.ptr; + // update the rest of the union members + data.size = total_size; + data.ptr = temp; + // transfer the rest of the data + memcpy(data.ptr + my_old_size, other.c_str(), other_size + 1); + } + } + + return *this; +} + +String String::operator+(const String& other) const { return String(*this) += other; } + +String::String(String&& other) { + memcpy(buf, other.buf, len); + other.buf[0] = '\0'; + other.setLast(); +} + +String& String::operator=(String&& other) { + if(this != &other) { + if(!isOnStack()) + delete[] data.ptr; + memcpy(buf, other.buf, len); + other.buf[0] = '\0'; + other.setLast(); + } + return *this; +} + +char String::operator[](unsigned i) const { + return const_cast(this)->operator[](i); // NOLINT +} + +char& String::operator[](unsigned i) { + if(isOnStack()) + return reinterpret_cast(buf)[i]; + return data.ptr[i]; +} + +DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wmaybe-uninitialized") +unsigned String::size() const { + if(isOnStack()) + return last - (unsigned(buf[last]) & 31); // using "last" would work only if "len" is 32 + return data.size; +} +DOCTEST_GCC_SUPPRESS_WARNING_POP + +unsigned String::capacity() const { + if(isOnStack()) + return len; + return data.capacity; +} + +int String::compare(const char* other, bool no_case) const { + if(no_case) + return stricmp(c_str(), other); + return std::strcmp(c_str(), other); +} + +int String::compare(const String& other, bool no_case) const { + return compare(other.c_str(), no_case); +} + +// clang-format off +bool operator==(const String& lhs, const String& rhs) { return lhs.compare(rhs) == 0; } +bool operator!=(const String& lhs, const String& rhs) { return lhs.compare(rhs) != 0; } +bool operator< (const String& lhs, const String& rhs) { return lhs.compare(rhs) < 0; } +bool operator> (const String& lhs, const String& rhs) { return lhs.compare(rhs) > 0; } +bool operator<=(const String& lhs, const String& rhs) { return (lhs != rhs) ? lhs.compare(rhs) < 0 : true; } +bool operator>=(const String& lhs, const String& rhs) { return (lhs != rhs) ? lhs.compare(rhs) > 0 : true; } +// clang-format on + +std::ostream& operator<<(std::ostream& s, const String& in) { return s << in.c_str(); } + +namespace { + void color_to_stream(std::ostream&, Color::Enum) DOCTEST_BRANCH_ON_DISABLED({}, ;) +} // namespace + +namespace Color { + std::ostream& operator<<(std::ostream& s, Color::Enum code) { + color_to_stream(s, code); + return s; + } +} // namespace Color + +// clang-format off +const char* assertString(assertType::Enum at) { + DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4062) // enum 'x' in switch of enum 'y' is not handled + switch(at) { //!OCLINT missing default in switch statements + case assertType::DT_WARN : return "WARN"; + case assertType::DT_CHECK : return "CHECK"; + case assertType::DT_REQUIRE : return "REQUIRE"; + + case assertType::DT_WARN_FALSE : return "WARN_FALSE"; + case assertType::DT_CHECK_FALSE : return "CHECK_FALSE"; + case assertType::DT_REQUIRE_FALSE : return "REQUIRE_FALSE"; + + case assertType::DT_WARN_THROWS : return "WARN_THROWS"; + case assertType::DT_CHECK_THROWS : return "CHECK_THROWS"; + case assertType::DT_REQUIRE_THROWS : return "REQUIRE_THROWS"; + + case assertType::DT_WARN_THROWS_AS : return "WARN_THROWS_AS"; + case assertType::DT_CHECK_THROWS_AS : return "CHECK_THROWS_AS"; + case assertType::DT_REQUIRE_THROWS_AS : return "REQUIRE_THROWS_AS"; + + case assertType::DT_WARN_THROWS_WITH : return "WARN_THROWS_WITH"; + case assertType::DT_CHECK_THROWS_WITH : return "CHECK_THROWS_WITH"; + case assertType::DT_REQUIRE_THROWS_WITH : return "REQUIRE_THROWS_WITH"; + + case assertType::DT_WARN_NOTHROW : return "WARN_NOTHROW"; + case assertType::DT_CHECK_NOTHROW : return "CHECK_NOTHROW"; + case assertType::DT_REQUIRE_NOTHROW : return "REQUIRE_NOTHROW"; + + case assertType::DT_WARN_EQ : return "WARN_EQ"; + case assertType::DT_CHECK_EQ : return "CHECK_EQ"; + case assertType::DT_REQUIRE_EQ : return "REQUIRE_EQ"; + case assertType::DT_WARN_NE : return "WARN_NE"; + case assertType::DT_CHECK_NE : return "CHECK_NE"; + case assertType::DT_REQUIRE_NE : return "REQUIRE_NE"; + case assertType::DT_WARN_GT : return "WARN_GT"; + case assertType::DT_CHECK_GT : return "CHECK_GT"; + case assertType::DT_REQUIRE_GT : return "REQUIRE_GT"; + case assertType::DT_WARN_LT : return "WARN_LT"; + case assertType::DT_CHECK_LT : return "CHECK_LT"; + case assertType::DT_REQUIRE_LT : return "REQUIRE_LT"; + case assertType::DT_WARN_GE : return "WARN_GE"; + case assertType::DT_CHECK_GE : return "CHECK_GE"; + case assertType::DT_REQUIRE_GE : return "REQUIRE_GE"; + case assertType::DT_WARN_LE : return "WARN_LE"; + case assertType::DT_CHECK_LE : return "CHECK_LE"; + case assertType::DT_REQUIRE_LE : return "REQUIRE_LE"; + + case assertType::DT_WARN_UNARY : return "WARN_UNARY"; + case assertType::DT_CHECK_UNARY : return "CHECK_UNARY"; + case assertType::DT_REQUIRE_UNARY : return "REQUIRE_UNARY"; + case assertType::DT_WARN_UNARY_FALSE : return "WARN_UNARY_FALSE"; + case assertType::DT_CHECK_UNARY_FALSE : return "CHECK_UNARY_FALSE"; + case assertType::DT_REQUIRE_UNARY_FALSE : return "REQUIRE_UNARY_FALSE"; + } + DOCTEST_MSVC_SUPPRESS_WARNING_POP + return ""; +} +// clang-format on + +const char* failureString(assertType::Enum at) { + if(at & assertType::is_warn) //!OCLINT bitwise operator in conditional + return "WARNING"; + if(at & assertType::is_check) //!OCLINT bitwise operator in conditional + return "ERROR"; + if(at & assertType::is_require) //!OCLINT bitwise operator in conditional + return "FATAL ERROR"; + return ""; +} + +DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wnull-dereference") +DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wnull-dereference") +// depending on the current options this will remove the path of filenames +const char* skipPathFromFilename(const char* file) { + if(getContextOptions()->no_path_in_filenames) { + auto back = std::strrchr(file, '\\'); + auto forward = std::strrchr(file, '/'); + if(back || forward) { + if(back > forward) + forward = back; + return forward + 1; + } + } + return file; +} +DOCTEST_CLANG_SUPPRESS_WARNING_POP +DOCTEST_GCC_SUPPRESS_WARNING_POP + +DOCTEST_DEFINE_DEFAULTS(TestCaseData); +DOCTEST_DEFINE_COPIES(TestCaseData); + +DOCTEST_DEFINE_DEFAULTS(AssertData); + +DOCTEST_DEFINE_DEFAULTS(MessageData); + +SubcaseSignature::SubcaseSignature(const char* name, const char* file, int line) + : m_name(name) + , m_file(file) + , m_line(line) {} + +DOCTEST_DEFINE_DEFAULTS(SubcaseSignature); +DOCTEST_DEFINE_COPIES(SubcaseSignature); + +bool SubcaseSignature::operator<(const SubcaseSignature& other) const { + if(m_line != other.m_line) + return m_line < other.m_line; + if(std::strcmp(m_file, other.m_file) != 0) + return std::strcmp(m_file, other.m_file) < 0; + return std::strcmp(m_name, other.m_name) < 0; +} + +IContextScope::IContextScope() = default; +IContextScope::~IContextScope() = default; + +DOCTEST_DEFINE_DEFAULTS(ContextOptions); + +#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +String toString(char* in) { return toString(static_cast(in)); } +String toString(const char* in) { return String("\"") + (in ? in : "{null string}") + "\""; } +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +String toString(bool in) { return in ? "true" : "false"; } +String toString(float in) { return fpToString(in, 5) + "f"; } +String toString(double in) { return fpToString(in, 10); } +String toString(double long in) { return fpToString(in, 15); } + +#define DOCTEST_TO_STRING_OVERLOAD(type, fmt) \ + String toString(type in) { \ + char buf[64]; \ + std::sprintf(buf, fmt, in); \ + return buf; \ + } + +DOCTEST_TO_STRING_OVERLOAD(char, "%d") +DOCTEST_TO_STRING_OVERLOAD(char signed, "%d") +DOCTEST_TO_STRING_OVERLOAD(char unsigned, "%u") +DOCTEST_TO_STRING_OVERLOAD(int short, "%d") +DOCTEST_TO_STRING_OVERLOAD(int short unsigned, "%u") +DOCTEST_TO_STRING_OVERLOAD(int, "%d") +DOCTEST_TO_STRING_OVERLOAD(unsigned, "%u") +DOCTEST_TO_STRING_OVERLOAD(int long, "%ld") +DOCTEST_TO_STRING_OVERLOAD(int long unsigned, "%lu") +DOCTEST_TO_STRING_OVERLOAD(int long long, "%lld") +DOCTEST_TO_STRING_OVERLOAD(int long long unsigned, "%llu") + +String toString(std::nullptr_t) { return "NULL"; } + +Approx::Approx(double value) + : m_epsilon(static_cast(std::numeric_limits::epsilon()) * 100) + , m_scale(1.0) + , m_value(value) {} + +DOCTEST_DEFINE_COPIES(Approx); + +Approx Approx::operator()(double value) const { + Approx approx(value); + approx.epsilon(m_epsilon); + approx.scale(m_scale); + return approx; +} + +Approx& Approx::epsilon(double newEpsilon) { + m_epsilon = newEpsilon; + return *this; +} +Approx& Approx::scale(double newScale) { + m_scale = newScale; + return *this; +} + +bool operator==(double lhs, const Approx& rhs) { + // Thanks to Richard Harris for his help refining this formula + return std::fabs(lhs - rhs.m_value) < + rhs.m_epsilon * (rhs.m_scale + std::max(std::fabs(lhs), std::fabs(rhs.m_value))); +} +bool operator==(const Approx& lhs, double rhs) { return operator==(rhs, lhs); } +bool operator!=(double lhs, const Approx& rhs) { return !operator==(lhs, rhs); } +bool operator!=(const Approx& lhs, double rhs) { return !operator==(rhs, lhs); } +bool operator<=(double lhs, const Approx& rhs) { return lhs < rhs.m_value || lhs == rhs; } +bool operator<=(const Approx& lhs, double rhs) { return lhs.m_value < rhs || lhs == rhs; } +bool operator>=(double lhs, const Approx& rhs) { return lhs > rhs.m_value || lhs == rhs; } +bool operator>=(const Approx& lhs, double rhs) { return lhs.m_value > rhs || lhs == rhs; } +bool operator<(double lhs, const Approx& rhs) { return lhs < rhs.m_value && lhs != rhs; } +bool operator<(const Approx& lhs, double rhs) { return lhs.m_value < rhs && lhs != rhs; } +bool operator>(double lhs, const Approx& rhs) { return lhs > rhs.m_value && lhs != rhs; } +bool operator>(const Approx& lhs, double rhs) { return lhs.m_value > rhs && lhs != rhs; } + +String toString(const Approx& in) { + return String("Approx( ") + doctest::toString(in.m_value) + " )"; +} +const ContextOptions* getContextOptions() { return DOCTEST_BRANCH_ON_DISABLED(nullptr, g_cs); } + +} // namespace doctest + +#ifdef DOCTEST_CONFIG_DISABLE +namespace doctest { +Context::Context(int, const char* const*) {} +Context::~Context() = default; +void Context::applyCommandLine(int, const char* const*) {} +void Context::addFilter(const char*, const char*) {} +void Context::clearFilters() {} +void Context::setOption(const char*, int) {} +void Context::setOption(const char*, const char*) {} +bool Context::shouldExit() { return false; } +void Context::setAsDefaultForAssertsOutOfTestCases() {} +void Context::setAssertHandler(detail::assert_handler) {} +int Context::run() { return 0; } + +DOCTEST_DEFINE_DEFAULTS(CurrentTestCaseStats); + +DOCTEST_DEFINE_DEFAULTS(TestRunStats); + +IReporter::~IReporter() = default; + +int IReporter::get_num_active_contexts() { return 0; } +const IContextScope* const* IReporter::get_active_contexts() { return nullptr; } +int IReporter::get_num_stringified_contexts() { return 0; } +const String* IReporter::get_stringified_contexts() { return nullptr; } + +int registerReporter(const char*, int, IReporter*) { return 0; } + +} // namespace doctest +#else // DOCTEST_CONFIG_DISABLE + +#if !defined(DOCTEST_CONFIG_COLORS_NONE) +#if !defined(DOCTEST_CONFIG_COLORS_WINDOWS) && !defined(DOCTEST_CONFIG_COLORS_ANSI) +#ifdef DOCTEST_PLATFORM_WINDOWS +#define DOCTEST_CONFIG_COLORS_WINDOWS +#else // linux +#define DOCTEST_CONFIG_COLORS_ANSI +#endif // platform +#endif // DOCTEST_CONFIG_COLORS_WINDOWS && DOCTEST_CONFIG_COLORS_ANSI +#endif // DOCTEST_CONFIG_COLORS_NONE + +namespace doctest_detail_test_suite_ns { +// holds the current test suite +doctest::detail::TestSuite& getCurrentTestSuite() { + static doctest::detail::TestSuite data; + return data; +} +} // namespace doctest_detail_test_suite_ns + +namespace doctest { +namespace { + // the int (priority) is part of the key for automatic sorting - sadly one can register a + // reporter with a duplicate name and a different priority but hopefully that won't happen often :| + typedef std::map, reporterCreatorFunc> reporterMap; + reporterMap& getReporters() { + static reporterMap data; + return data; + } +} // namespace +namespace detail { +#define DOCTEST_ITERATE_THROUGH_REPORTERS(function, ...) \ + for(auto& curr_rep : g_cs->reporters_currently_used) \ + curr_rep->function(__VA_ARGS__) + + DOCTEST_DEFINE_DEFAULTS(TestFailureException); + DOCTEST_DEFINE_COPIES(TestFailureException); + bool checkIfShouldThrow(assertType::Enum at) { + if(at & assertType::is_require) //!OCLINT bitwise operator in conditional + return true; + + if((at & assertType::is_check) //!OCLINT bitwise operator in conditional + && getContextOptions()->abort_after > 0 && + (g_cs->numAssertsFailed + g_cs->numAssertsFailedCurrentTest_atomic) >= + getContextOptions()->abort_after) + return true; + + return false; + } + + void throwException() { +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + throw TestFailureException(); +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + } +} // namespace detail + +namespace { + using namespace detail; + // matching of a string against a wildcard mask (case sensitivity configurable) taken from + // https://www.codeproject.com/Articles/1088/Wildcard-string-compare-globbing + int wildcmp(const char* str, const char* wild, bool caseSensitive) { + const char* cp = nullptr; + const char* mp = nullptr; + + while((*str) && (*wild != '*')) { + if((caseSensitive ? (*wild != *str) : (tolower(*wild) != tolower(*str))) && + (*wild != '?')) { + return 0; + } + wild++; + str++; + } + + while(*str) { + if(*wild == '*') { + if(!*++wild) { + return 1; + } + mp = wild; + cp = str + 1; + } else if((caseSensitive ? (*wild == *str) : (tolower(*wild) == tolower(*str))) || + (*wild == '?')) { + wild++; + str++; + } else { + wild = mp; //!OCLINT parameter reassignment + str = cp++; //!OCLINT parameter reassignment + } + } + + while(*wild == '*') { + wild++; + } + return !*wild; + } + + //// C string hash function (djb2) - taken from http://www.cse.yorku.ca/~oz/hash.html + //unsigned hashStr(unsigned const char* str) { + // unsigned long hash = 5381; + // char c; + // while((c = *str++)) + // hash = ((hash << 5) + hash) + c; // hash * 33 + c + // return hash; + //} + + // checks if the name matches any of the filters (and can be configured what to do when empty) + bool matchesAny(const char* name, const std::vector& filters, bool matchEmpty, + bool caseSensitive) { + if(filters.empty() && matchEmpty) + return true; + for(auto& curr : filters) + if(wildcmp(name, curr.c_str(), caseSensitive)) + return true; + return false; + } +} // namespace +namespace detail { + + Subcase::Subcase(const char* name, const char* file, int line) + : m_signature(name, file, line) { + ContextState* s = g_cs; + + // if we have already completed it + if(s->subcasesPassed.count(m_signature) != 0) + return; + + // check subcase filters + if(s->subcasesCurrentLevel < s->subcase_filter_levels) { + if(!matchesAny(m_signature.m_name, s->filters[6], true, s->case_sensitive)) + return; + if(matchesAny(m_signature.m_name, s->filters[7], false, s->case_sensitive)) + return; + } + + // if a Subcase on the same level has already been entered + if(s->subcasesEnteredLevels.count(s->subcasesCurrentLevel) != 0) { + s->should_reenter = true; + return; + } + + s->subcasesEnteredLevels.insert(s->subcasesCurrentLevel++); + m_entered = true; + + DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_start, m_signature); + } + + Subcase::~Subcase() { + if(m_entered) { + ContextState* s = g_cs; + + s->subcasesCurrentLevel--; + // only mark the subcase as passed if no subcases have been skipped + if(s->should_reenter == false) + s->subcasesPassed.insert(m_signature); + + DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_end, DOCTEST_EMPTY); + } + } + + Subcase::operator bool() const { return m_entered; } + + Result::Result(bool passed, const String& decomposition) + : m_passed(passed) + , m_decomp(decomposition) {} + + DOCTEST_DEFINE_DEFAULTS(Result); + DOCTEST_DEFINE_COPIES(Result); + + ExpressionDecomposer::ExpressionDecomposer(assertType::Enum at) + : m_at(at) {} + + DOCTEST_DEFINE_DEFAULTS(ExpressionDecomposer); + + DOCTEST_DEFINE_DEFAULTS(TestSuite); + DOCTEST_DEFINE_COPIES(TestSuite); + + TestSuite& TestSuite::operator*(const char* in) { + m_test_suite = in; + // clear state + m_description = nullptr; + m_skip = false; + m_may_fail = false; + m_should_fail = false; + m_expected_failures = 0; + m_timeout = 0; + return *this; + } + + TestCase::TestCase(funcType test, const char* file, unsigned line, const TestSuite& test_suite, + const char* type, int template_id) { + m_file = file; + m_line = line; + m_name = nullptr; + m_test_suite = test_suite.m_test_suite; + m_description = test_suite.m_description; + m_skip = test_suite.m_skip; + m_may_fail = test_suite.m_may_fail; + m_should_fail = test_suite.m_should_fail; + m_expected_failures = test_suite.m_expected_failures; + m_timeout = test_suite.m_timeout; + + m_test = test; + m_type = type; + m_template_id = template_id; + } + + DOCTEST_DEFINE_DEFAULTS(TestCase); + + TestCase::TestCase(const TestCase& other) + : TestCaseData() { + *this = other; + } + + DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(26434) // hides a non-virtual function + DOCTEST_MSVC_SUPPRESS_WARNING(26437) // Do not slice + TestCase& TestCase::operator=(const TestCase& other) { + static_cast(*this) = static_cast(other); + + m_test = other.m_test; + m_type = other.m_type; + m_template_id = other.m_template_id; + m_full_name = other.m_full_name; + + if(m_template_id != -1) + m_name = m_full_name.c_str(); + return *this; + } + DOCTEST_MSVC_SUPPRESS_WARNING_POP + + TestCase& TestCase::operator*(const char* in) { + m_name = in; + // make a new name with an appended type for templated test case + if(m_template_id != -1) { + m_full_name = String(m_name) + m_type; + // redirect the name to point to the newly constructed full name + m_name = m_full_name.c_str(); + } + return *this; + } + + bool TestCase::operator<(const TestCase& other) const { + if(m_line != other.m_line) + return m_line < other.m_line; + const int file_cmp = std::strcmp(m_file, other.m_file); + if(file_cmp != 0) + return file_cmp < 0; + return m_template_id < other.m_template_id; + } +} // namespace detail +namespace { + using namespace detail; + // for sorting tests by file/line + bool fileOrderComparator(const TestCase* lhs, const TestCase* rhs) { +#if DOCTEST_MSVC + // this is needed because MSVC gives different case for drive letters + // for __FILE__ when evaluated in a header and a source file + const int res = stricmp(lhs->m_file, rhs->m_file); +#else // MSVC + const int res = std::strcmp(lhs->m_file, rhs->m_file); +#endif // MSVC + if(res != 0) + return res < 0; + if(lhs->m_line != rhs->m_line) + return lhs->m_line < rhs->m_line; + return lhs->m_template_id < rhs->m_template_id; + } + + // for sorting tests by suite/file/line + bool suiteOrderComparator(const TestCase* lhs, const TestCase* rhs) { + const int res = std::strcmp(lhs->m_test_suite, rhs->m_test_suite); + if(res != 0) + return res < 0; + return fileOrderComparator(lhs, rhs); + } + + // for sorting tests by name/suite/file/line + bool nameOrderComparator(const TestCase* lhs, const TestCase* rhs) { + const int res = std::strcmp(lhs->m_name, rhs->m_name); + if(res != 0) + return res < 0; + return suiteOrderComparator(lhs, rhs); + } + + // all the registered tests + std::set& getRegisteredTests() { + static std::set data; + return data; + } + +#ifdef DOCTEST_CONFIG_COLORS_WINDOWS + HANDLE g_stdoutHandle; + WORD g_origFgAttrs; + WORD g_origBgAttrs; + bool g_attrsInitted = false; + + int colors_init() { + if(!g_attrsInitted) { + g_stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE); + g_attrsInitted = true; + CONSOLE_SCREEN_BUFFER_INFO csbiInfo; + GetConsoleScreenBufferInfo(g_stdoutHandle, &csbiInfo); + g_origFgAttrs = csbiInfo.wAttributes & ~(BACKGROUND_GREEN | BACKGROUND_RED | + BACKGROUND_BLUE | BACKGROUND_INTENSITY); + g_origBgAttrs = csbiInfo.wAttributes & ~(FOREGROUND_GREEN | FOREGROUND_RED | + FOREGROUND_BLUE | FOREGROUND_INTENSITY); + } + return 0; + } + + int dumy_init_console_colors = colors_init(); +#endif // DOCTEST_CONFIG_COLORS_WINDOWS + + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") + void color_to_stream(std::ostream& s, Color::Enum code) { + ((void)s); // for DOCTEST_CONFIG_COLORS_NONE or DOCTEST_CONFIG_COLORS_WINDOWS + ((void)code); // for DOCTEST_CONFIG_COLORS_NONE +#ifdef DOCTEST_CONFIG_COLORS_ANSI + if(g_no_colors || + (isatty(STDOUT_FILENO) == false && getContextOptions()->force_colors == false)) + return; + + auto col = ""; + // clang-format off + switch(code) { //!OCLINT missing break in switch statement / unnecessary default statement in covered switch statement + case Color::Red: col = "[0;31m"; break; + case Color::Green: col = "[0;32m"; break; + case Color::Blue: col = "[0;34m"; break; + case Color::Cyan: col = "[0;36m"; break; + case Color::Yellow: col = "[0;33m"; break; + case Color::Grey: col = "[1;30m"; break; + case Color::LightGrey: col = "[0;37m"; break; + case Color::BrightRed: col = "[1;31m"; break; + case Color::BrightGreen: col = "[1;32m"; break; + case Color::BrightWhite: col = "[1;37m"; break; + case Color::Bright: // invalid + case Color::None: + case Color::White: + default: col = "[0m"; + } + // clang-format on + s << "\033" << col; +#endif // DOCTEST_CONFIG_COLORS_ANSI + +#ifdef DOCTEST_CONFIG_COLORS_WINDOWS + if(g_no_colors || + (isatty(fileno(stdout)) == false && getContextOptions()->force_colors == false)) + return; + +#define DOCTEST_SET_ATTR(x) SetConsoleTextAttribute(g_stdoutHandle, x | g_origBgAttrs) + + // clang-format off + switch (code) { + case Color::White: DOCTEST_SET_ATTR(FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE); break; + case Color::Red: DOCTEST_SET_ATTR(FOREGROUND_RED); break; + case Color::Green: DOCTEST_SET_ATTR(FOREGROUND_GREEN); break; + case Color::Blue: DOCTEST_SET_ATTR(FOREGROUND_BLUE); break; + case Color::Cyan: DOCTEST_SET_ATTR(FOREGROUND_BLUE | FOREGROUND_GREEN); break; + case Color::Yellow: DOCTEST_SET_ATTR(FOREGROUND_RED | FOREGROUND_GREEN); break; + case Color::Grey: DOCTEST_SET_ATTR(0); break; + case Color::LightGrey: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY); break; + case Color::BrightRed: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_RED); break; + case Color::BrightGreen: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_GREEN); break; + case Color::BrightWhite: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE); break; + case Color::None: + case Color::Bright: // invalid + default: DOCTEST_SET_ATTR(g_origFgAttrs); + } + // clang-format on +#endif // DOCTEST_CONFIG_COLORS_WINDOWS + } + DOCTEST_CLANG_SUPPRESS_WARNING_POP + + std::vector& getExceptionTranslators() { + static std::vector data; + return data; + } + + String translateActiveException() { +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + String res; + auto& translators = getExceptionTranslators(); + for(auto& curr : translators) + if(curr->translate(res)) + return res; + // clang-format off + try { + throw; + } catch(std::exception& ex) { + return ex.what(); + } catch(std::string& msg) { + return msg.c_str(); + } catch(const char* msg) { + return msg; + } catch(...) { + return "unknown exception"; + } +// clang-format on +#else // DOCTEST_CONFIG_NO_EXCEPTIONS + return ""; +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + } +} // namespace + +namespace detail { + // used by the macros for registering tests + int regTest(const TestCase& tc) { + getRegisteredTests().insert(tc); + return 0; + } + + // sets the current test suite + int setTestSuite(const TestSuite& ts) { + doctest_detail_test_suite_ns::getCurrentTestSuite() = ts; + return 0; + } + +#ifdef DOCTEST_PLATFORM_MAC + // The following function is taken directly from the following technical note: + // http://developer.apple.com/library/mac/#qa/qa2004/qa1361.html + // Returns true if the current process is being debugged (either + // running under the debugger or has a debugger attached post facto). + bool isDebuggerActive() { + int mib[4]; + kinfo_proc info; + size_t size; + // Initialize the flags so that, if sysctl fails for some bizarre + // reason, we get a predictable result. + info.kp_proc.p_flag = 0; + // Initialize mib, which tells sysctl the info we want, in this case + // we're looking for information about a specific process ID. + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = getpid(); + // Call sysctl. + size = sizeof(info); + if(sysctl(mib, DOCTEST_COUNTOF(mib), &info, &size, 0, 0) != 0) { + std::cerr << "\nCall to sysctl failed - unable to determine if debugger is active **\n"; + return false; + } + // We're being debugged if the P_TRACED flag is set. + return ((info.kp_proc.p_flag & P_TRACED) != 0); + } +#elif DOCTEST_MSVC || defined(__MINGW32__) + bool isDebuggerActive() { return ::IsDebuggerPresent() != 0; } +#else + bool isDebuggerActive() { return false; } +#endif // Platform + + void registerExceptionTranslatorImpl(const IExceptionTranslator* et) { + if(std::find(getExceptionTranslators().begin(), getExceptionTranslators().end(), et) == + getExceptionTranslators().end()) + getExceptionTranslators().push_back(et); + } + +#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + void toStream(std::ostream* s, char* in) { *s << in; } + void toStream(std::ostream* s, const char* in) { *s << in; } +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + void toStream(std::ostream* s, bool in) { *s << std::boolalpha << in << std::noboolalpha; } + void toStream(std::ostream* s, float in) { *s << in; } + void toStream(std::ostream* s, double in) { *s << in; } + void toStream(std::ostream* s, double long in) { *s << in; } + + void toStream(std::ostream* s, char in) { *s << in; } + void toStream(std::ostream* s, char signed in) { *s << in; } + void toStream(std::ostream* s, char unsigned in) { *s << in; } + void toStream(std::ostream* s, int short in) { *s << in; } + void toStream(std::ostream* s, int short unsigned in) { *s << in; } + void toStream(std::ostream* s, int in) { *s << in; } + void toStream(std::ostream* s, int unsigned in) { *s << in; } + void toStream(std::ostream* s, int long in) { *s << in; } + void toStream(std::ostream* s, int long unsigned in) { *s << in; } + void toStream(std::ostream* s, int long long in) { *s << in; } + void toStream(std::ostream* s, int long long unsigned in) { *s << in; } + + DOCTEST_THREAD_LOCAL std::vector g_infoContexts; // for logging with INFO() + + ContextBuilder::ICapture::ICapture() = default; + ContextBuilder::ICapture::~ICapture() = default; + + ContextBuilder::Chunk::Chunk() = default; + ContextBuilder::Chunk::~Chunk() = default; + + ContextBuilder::Node::Node() = default; + ContextBuilder::Node::~Node() = default; + + // steal the contents of the other - acting as a move constructor... + ContextBuilder::ContextBuilder(ContextBuilder& other) + : numCaptures(other.numCaptures) + , head(other.head) + , tail(other.tail) { + other.numCaptures = 0; + other.head = nullptr; + other.tail = nullptr; + memcpy(stackChunks, other.stackChunks, + unsigned(int(sizeof(Chunk)) * DOCTEST_CONFIG_NUM_CAPTURES_ON_STACK)); + } + + DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wcast-align") + void ContextBuilder::stringify(std::ostream* s) const { + int curr = 0; + // iterate over small buffer + while(curr < numCaptures && curr < DOCTEST_CONFIG_NUM_CAPTURES_ON_STACK) + reinterpret_cast(stackChunks[curr++].buf)->toStream(s); + // iterate over list + auto curr_elem = head; + while(curr < numCaptures) { + reinterpret_cast(curr_elem->chunk.buf)->toStream(s); + curr_elem = curr_elem->next; + ++curr; + } + } + DOCTEST_GCC_SUPPRESS_WARNING_POP + + ContextBuilder::ContextBuilder() = default; + + ContextBuilder::~ContextBuilder() { + // free the linked list - the ones on the stack are left as-is + // no destructors are called at all - there is no need + while(head) { + auto next = head->next; + delete head; + head = next; + } + } + + ContextScope::ContextScope(ContextBuilder& temp) + : contextBuilder(temp) { + g_infoContexts.push_back(this); + } + + DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4996) // std::uncaught_exception is deprecated in C++17 + DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") + ContextScope::~ContextScope() { + if(std::uncaught_exception()) { + std::ostringstream s; + this->stringify(&s); + g_cs->stringifiedContexts.push_back(s.str().c_str()); + } + g_infoContexts.pop_back(); + } + DOCTEST_CLANG_SUPPRESS_WARNING_POP + DOCTEST_GCC_SUPPRESS_WARNING_POP + DOCTEST_MSVC_SUPPRESS_WARNING_POP + + void ContextScope::stringify(std::ostream* s) const { contextBuilder.stringify(s); } +} // namespace detail +namespace { + using namespace detail; + + std::ostream& file_line_to_stream(std::ostream& s, const char* file, int line, + const char* tail = "") { + const auto opt = getContextOptions(); + s << Color::LightGrey << skipPathFromFilename(file) << (opt->gnu_file_line ? ":" : "(") + << (opt->no_line_numbers ? 0 : line) // 0 or the real num depending on the option + << (opt->gnu_file_line ? ":" : "):") << tail; + return s; + } + +#if !defined(DOCTEST_CONFIG_POSIX_SIGNALS) && !defined(DOCTEST_CONFIG_WINDOWS_SEH) + struct FatalConditionHandler + { + void reset() {} + }; +#else // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH + + void reportFatal(const std::string&); + +#ifdef DOCTEST_PLATFORM_WINDOWS + + struct SignalDefs + { + DWORD id; + const char* name; + }; + // There is no 1-1 mapping between signals and windows exceptions. + // Windows can easily distinguish between SO and SigSegV, + // but SigInt, SigTerm, etc are handled differently. + SignalDefs signalDefs[] = { + {EXCEPTION_ILLEGAL_INSTRUCTION, "SIGILL - Illegal instruction signal"}, + {EXCEPTION_STACK_OVERFLOW, "SIGSEGV - Stack overflow"}, + {EXCEPTION_ACCESS_VIOLATION, "SIGSEGV - Segmentation violation signal"}, + {EXCEPTION_INT_DIVIDE_BY_ZERO, "Divide by zero error"}, + }; + + struct FatalConditionHandler + { + static LONG CALLBACK handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo) { + for(size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) { + if(ExceptionInfo->ExceptionRecord->ExceptionCode == signalDefs[i].id) { + reportFatal(signalDefs[i].name); + } + } + // If its not an exception we care about, pass it along. + // This stops us from eating debugger breaks etc. + return EXCEPTION_CONTINUE_SEARCH; + } + + FatalConditionHandler() { + isSet = true; + // 32k seems enough for doctest to handle stack overflow, + // but the value was found experimentally, so there is no strong guarantee + guaranteeSize = 32 * 1024; + exceptionHandlerHandle = nullptr; + // Register as first handler in current chain + exceptionHandlerHandle = AddVectoredExceptionHandler(1, handleVectoredException); + // Pass in guarantee size to be filled + SetThreadStackGuarantee(&guaranteeSize); + } + + static void reset() { + if(isSet) { + // Unregister handler and restore the old guarantee + RemoveVectoredExceptionHandler(exceptionHandlerHandle); + SetThreadStackGuarantee(&guaranteeSize); + exceptionHandlerHandle = nullptr; + isSet = false; + } + } + + ~FatalConditionHandler() { reset(); } + + private: + static bool isSet; + static ULONG guaranteeSize; + static PVOID exceptionHandlerHandle; + }; + + bool FatalConditionHandler::isSet = false; + ULONG FatalConditionHandler::guaranteeSize = 0; + PVOID FatalConditionHandler::exceptionHandlerHandle = nullptr; + +#else // DOCTEST_PLATFORM_WINDOWS + + struct SignalDefs + { + int id; + const char* name; + }; + SignalDefs signalDefs[] = {{SIGINT, "SIGINT - Terminal interrupt signal"}, + {SIGILL, "SIGILL - Illegal instruction signal"}, + {SIGFPE, "SIGFPE - Floating point error signal"}, + {SIGSEGV, "SIGSEGV - Segmentation violation signal"}, + {SIGTERM, "SIGTERM - Termination request signal"}, + {SIGABRT, "SIGABRT - Abort (abnormal termination) signal"}}; + + struct FatalConditionHandler + { + static bool isSet; + static struct sigaction oldSigActions[DOCTEST_COUNTOF(signalDefs)]; + static stack_t oldSigStack; + static char altStackMem[4 * SIGSTKSZ]; + + static void handleSignal(int sig) { + const char* name = ""; + for(std::size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) { + SignalDefs& def = signalDefs[i]; + if(sig == def.id) { + name = def.name; + break; + } + } + reset(); + reportFatal(name); + raise(sig); + } + + FatalConditionHandler() { + isSet = true; + stack_t sigStack; + sigStack.ss_sp = altStackMem; + sigStack.ss_size = sizeof(altStackMem); + sigStack.ss_flags = 0; + sigaltstack(&sigStack, &oldSigStack); + struct sigaction sa = {}; + sa.sa_handler = handleSignal; // NOLINT + sa.sa_flags = SA_ONSTACK; + for(std::size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) { + sigaction(signalDefs[i].id, &sa, &oldSigActions[i]); + } + } + + ~FatalConditionHandler() { reset(); } + static void reset() { + if(isSet) { + // Set signals back to previous values -- hopefully nobody overwrote them in the meantime + for(std::size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) { + sigaction(signalDefs[i].id, &oldSigActions[i], nullptr); + } + // Return the old stack + sigaltstack(&oldSigStack, nullptr); + isSet = false; + } + } + }; + + bool FatalConditionHandler::isSet = false; + struct sigaction FatalConditionHandler::oldSigActions[DOCTEST_COUNTOF(signalDefs)] = {}; + stack_t FatalConditionHandler::oldSigStack = {}; + char FatalConditionHandler::altStackMem[] = {}; + +#endif // DOCTEST_PLATFORM_WINDOWS +#endif // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH + +} // namespace + +namespace { + using namespace detail; + +#ifdef DOCTEST_PLATFORM_WINDOWS +#define DOCTEST_OUTPUT_DEBUG_STRING(text) ::OutputDebugStringA(text) +#else + // TODO: integration with XCode and other IDEs +#define DOCTEST_OUTPUT_DEBUG_STRING(text) +#endif // Platform + + void addAssert(assertType::Enum at) { + if((at & assertType::is_warn) == 0) //!OCLINT bitwise operator in conditional + g_cs->numAssertsCurrentTest_atomic++; + } + + void addFailedAssert(assertType::Enum at) { + if((at & assertType::is_warn) == 0) //!OCLINT bitwise operator in conditional + g_cs->numAssertsFailedCurrentTest_atomic++; + } + +#if defined(DOCTEST_CONFIG_POSIX_SIGNALS) || defined(DOCTEST_CONFIG_WINDOWS_SEH) + void reportFatal(const std::string& message) { + g_cs->failure_flags |= TestCaseFailureReason::Crash; + + DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_exception, {message.c_str(), true}); + + while(g_cs->subcasesCurrentLevel--) + DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_end, DOCTEST_EMPTY); + + g_cs->finalizeTestCaseData(); + + DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_end, *g_cs); + + DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_end, *g_cs); + } +#endif // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH +} // namespace +namespace detail { + + ResultBuilder::ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr, + const char* exception_type) { + m_test_case = g_cs->currentTest; + m_at = at; + m_file = file; + m_line = line; + m_expr = expr; + m_failed = true; + m_threw = false; + m_threw_as = false; + m_exception_type = exception_type; +#if DOCTEST_MSVC + if(m_expr[0] == ' ') // this happens when variadic macros are disabled under MSVC + ++m_expr; +#endif // MSVC + } + + DOCTEST_DEFINE_DEFAULTS(ResultBuilder); + + void ResultBuilder::setResult(const Result& res) { + m_decomp = res.m_decomp; + m_failed = !res.m_passed; + } + + void ResultBuilder::translateException() { + m_threw = true; + m_exception = translateActiveException(); + } + + bool ResultBuilder::log() { + if(m_at & assertType::is_throws) { //!OCLINT bitwise operator in conditional + m_failed = !m_threw; + } else if(m_at & assertType::is_throws_as) { //!OCLINT bitwise operator in conditional + m_failed = !m_threw_as; + } else if(m_at & assertType::is_throws_with) { //!OCLINT bitwise operator in conditional + m_failed = m_exception != m_exception_type; + } else if(m_at & assertType::is_nothrow) { //!OCLINT bitwise operator in conditional + m_failed = m_threw; + } + + if(m_exception.size()) + m_exception = String("\"") + m_exception + "\""; + + if(is_running_in_test) { + addAssert(m_at); + DOCTEST_ITERATE_THROUGH_REPORTERS(log_assert, *this); + + if(m_failed) + addFailedAssert(m_at); + } else if(m_failed) { + failed_out_of_a_testing_context(*this); + } + + return m_failed && isDebuggerActive() && + !getContextOptions()->no_breaks; // break into debugger + } + + void ResultBuilder::react() const { + if(m_failed && checkIfShouldThrow(m_at)) + throwException(); + } + + void failed_out_of_a_testing_context(const AssertData& ad) { + if(g_cs->ah) + g_cs->ah(ad); + else + std::abort(); + } + + void decomp_assert(assertType::Enum at, const char* file, int line, const char* expr, + Result result) { + bool failed = !result.m_passed; + + // ################################################################################### + // IF THE DEBUGGER BREAKS HERE - GO 1 LEVEL UP IN THE CALLSTACK FOR THE FAILING ASSERT + // THIS IS THE EFFECT OF HAVING 'DOCTEST_CONFIG_SUPER_FAST_ASSERTS' DEFINED + // ################################################################################### + DOCTEST_ASSERT_OUT_OF_TESTS(result.m_decomp); + DOCTEST_ASSERT_IN_TESTS(result.m_decomp); + } + + MessageBuilder::MessageBuilder(const char* file, int line, assertType::Enum severity) { + m_stream = getTlsOss(); + m_file = file; + m_line = line; + m_severity = severity; + } + + IExceptionTranslator::IExceptionTranslator() = default; + IExceptionTranslator::~IExceptionTranslator() = default; + + bool MessageBuilder::log() { + m_string = getTlsOssResult(); + DOCTEST_ITERATE_THROUGH_REPORTERS(log_message, *this); + + const bool isWarn = m_severity & assertType::is_warn; + + // warn is just a message in this context so we dont treat it as an assert + if(!isWarn) { + addAssert(m_severity); + addFailedAssert(m_severity); + } + + return isDebuggerActive() && !getContextOptions()->no_breaks && !isWarn; // break + } + + void MessageBuilder::react() { + if(m_severity & assertType::is_require) //!OCLINT bitwise operator in conditional + throwException(); + } + + MessageBuilder::~MessageBuilder() = default; +} // namespace detail +namespace { + using namespace detail; + + template + [[noreturn]] void throw_exception(Ex const& e) { +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + throw e; +#else // DOCTEST_CONFIG_NO_EXCEPTIONS + std::cerr << "doctest will terminate because it needed to throw an exception.\n" + << "The message was: " << e.what() << '\n'; + std::terminate(); +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + } + +#define DOCTEST_INTERNAL_ERROR(msg) \ + throw_exception(std::logic_error( \ + __FILE__ ":" DOCTEST_TOSTR(__LINE__) ": Internal doctest error: " msg)) + + // clang-format off + +// ================================================================================================= +// The following code has been taken verbatim from Catch2/include/internal/catch_xmlwriter.h/cpp +// This is done so cherry-picking bug fixes is trivial - even the style/formatting is untouched. +// ================================================================================================= + + class XmlEncode { + public: + enum ForWhat { ForTextNodes, ForAttributes }; + + XmlEncode( std::string const& str, ForWhat forWhat = ForTextNodes ); + + void encodeTo( std::ostream& os ) const; + + friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ); + + private: + std::string m_str; + ForWhat m_forWhat; + }; + + class XmlWriter { + public: + + class ScopedElement { + public: + ScopedElement( XmlWriter* writer ); + + ScopedElement( ScopedElement&& other ) noexcept; + ScopedElement& operator=( ScopedElement&& other ) noexcept; + + ~ScopedElement(); + + ScopedElement& writeText( std::string const& text, bool indent = true ); + + template + ScopedElement& writeAttribute( std::string const& name, T const& attribute ) { + m_writer->writeAttribute( name, attribute ); + return *this; + } + + private: + mutable XmlWriter* m_writer = nullptr; + }; + + XmlWriter( std::ostream& os = std::cout ); + ~XmlWriter(); + + XmlWriter( XmlWriter const& ) = delete; + XmlWriter& operator=( XmlWriter const& ) = delete; + + XmlWriter& startElement( std::string const& name ); + + ScopedElement scopedElement( std::string const& name ); + + XmlWriter& endElement(); + + XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ); + + XmlWriter& writeAttribute( std::string const& name, const char* attribute ); + + XmlWriter& writeAttribute( std::string const& name, bool attribute ); + + template + XmlWriter& writeAttribute( std::string const& name, T const& attribute ) { + std::stringstream rss; + rss << attribute; + return writeAttribute( name, rss.str() ); + } + + XmlWriter& writeText( std::string const& text, bool indent = true ); + + //XmlWriter& writeComment( std::string const& text ); + + //void writeStylesheetRef( std::string const& url ); + + //XmlWriter& writeBlankLine(); + + void ensureTagClosed(); + + private: + + void writeDeclaration(); + + void newlineIfNecessary(); + + bool m_tagIsOpen = false; + bool m_needsNewline = false; + std::vector m_tags; + std::string m_indent; + std::ostream& m_os; + }; + +// ================================================================================================= +// The following code has been taken verbatim from Catch2/include/internal/catch_xmlwriter.h/cpp +// This is done so cherry-picking bug fixes is trivial - even the style/formatting is untouched. +// ================================================================================================= + +using uchar = unsigned char; + +namespace { + + size_t trailingBytes(unsigned char c) { + if ((c & 0xE0) == 0xC0) { + return 2; + } + if ((c & 0xF0) == 0xE0) { + return 3; + } + if ((c & 0xF8) == 0xF0) { + return 4; + } + DOCTEST_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered"); + } + + uint32_t headerValue(unsigned char c) { + if ((c & 0xE0) == 0xC0) { + return c & 0x1F; + } + if ((c & 0xF0) == 0xE0) { + return c & 0x0F; + } + if ((c & 0xF8) == 0xF0) { + return c & 0x07; + } + DOCTEST_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered"); + } + + void hexEscapeChar(std::ostream& os, unsigned char c) { + std::ios_base::fmtflags f(os.flags()); + os << "\\x" + << std::uppercase << std::hex << std::setfill('0') << std::setw(2) + << static_cast(c); + os.flags(f); + } + +} // anonymous namespace + + XmlEncode::XmlEncode( std::string const& str, ForWhat forWhat ) + : m_str( str ), + m_forWhat( forWhat ) + {} + + void XmlEncode::encodeTo( std::ostream& os ) const { + // Apostrophe escaping not necessary if we always use " to write attributes + // (see: http://www.w3.org/TR/xml/#syntax) + + for( std::size_t idx = 0; idx < m_str.size(); ++ idx ) { + uchar c = m_str[idx]; + switch (c) { + case '<': os << "<"; break; + case '&': os << "&"; break; + + case '>': + // See: http://www.w3.org/TR/xml/#syntax + if (idx > 2 && m_str[idx - 1] == ']' && m_str[idx - 2] == ']') + os << ">"; + else + os << c; + break; + + case '\"': + if (m_forWhat == ForAttributes) + os << """; + else + os << c; + break; + + default: + // Check for control characters and invalid utf-8 + + // Escape control characters in standard ascii + // see http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0 + if (c < 0x09 || (c > 0x0D && c < 0x20) || c == 0x7F) { + hexEscapeChar(os, c); + break; + } + + // Plain ASCII: Write it to stream + if (c < 0x7F) { + os << c; + break; + } + + // UTF-8 territory + // Check if the encoding is valid and if it is not, hex escape bytes. + // Important: We do not check the exact decoded values for validity, only the encoding format + // First check that this bytes is a valid lead byte: + // This means that it is not encoded as 1111 1XXX + // Or as 10XX XXXX + if (c < 0xC0 || + c >= 0xF8) { + hexEscapeChar(os, c); + break; + } + + auto encBytes = trailingBytes(c); + // Are there enough bytes left to avoid accessing out-of-bounds memory? + if (idx + encBytes - 1 >= m_str.size()) { + hexEscapeChar(os, c); + break; + } + // The header is valid, check data + // The next encBytes bytes must together be a valid utf-8 + // This means: bitpattern 10XX XXXX and the extracted value is sane (ish) + bool valid = true; + uint32_t value = headerValue(c); + for (std::size_t n = 1; n < encBytes; ++n) { + uchar nc = m_str[idx + n]; + valid &= ((nc & 0xC0) == 0x80); + value = (value << 6) | (nc & 0x3F); + } + + if ( + // Wrong bit pattern of following bytes + (!valid) || + // Overlong encodings + (value < 0x80) || + ( value < 0x800 && encBytes > 2) || // removed "0x80 <= value &&" because redundant + (0x800 < value && value < 0x10000 && encBytes > 3) || + // Encoded value out of range + (value >= 0x110000) + ) { + hexEscapeChar(os, c); + break; + } + + // If we got here, this is in fact a valid(ish) utf-8 sequence + for (std::size_t n = 0; n < encBytes; ++n) { + os << m_str[idx + n]; + } + idx += encBytes - 1; + break; + } + } + } + + std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) { + xmlEncode.encodeTo( os ); + return os; + } + + XmlWriter::ScopedElement::ScopedElement( XmlWriter* writer ) + : m_writer( writer ) + {} + + XmlWriter::ScopedElement::ScopedElement( ScopedElement&& other ) noexcept + : m_writer( other.m_writer ){ + other.m_writer = nullptr; + } + XmlWriter::ScopedElement& XmlWriter::ScopedElement::operator=( ScopedElement&& other ) noexcept { + if ( m_writer ) { + m_writer->endElement(); + } + m_writer = other.m_writer; + other.m_writer = nullptr; + return *this; + } + + + XmlWriter::ScopedElement::~ScopedElement() { + if( m_writer ) + m_writer->endElement(); + } + + XmlWriter::ScopedElement& XmlWriter::ScopedElement::writeText( std::string const& text, bool indent ) { + m_writer->writeText( text, indent ); + return *this; + } + + XmlWriter::XmlWriter( std::ostream& os ) : m_os( os ) + { + writeDeclaration(); + } + + XmlWriter::~XmlWriter() { + while( !m_tags.empty() ) + endElement(); + } + + XmlWriter& XmlWriter::startElement( std::string const& name ) { + ensureTagClosed(); + newlineIfNecessary(); + m_os << m_indent << '<' << name; + m_tags.push_back( name ); + m_indent += " "; + m_tagIsOpen = true; + return *this; + } + + XmlWriter::ScopedElement XmlWriter::scopedElement( std::string const& name ) { + ScopedElement scoped( this ); + startElement( name ); + return scoped; + } + + XmlWriter& XmlWriter::endElement() { + newlineIfNecessary(); + m_indent = m_indent.substr( 0, m_indent.size()-2 ); + if( m_tagIsOpen ) { + m_os << "/>"; + m_tagIsOpen = false; + } + else { + m_os << m_indent << ""; + } + m_os << std::endl; + m_tags.pop_back(); + return *this; + } + + XmlWriter& XmlWriter::writeAttribute( std::string const& name, std::string const& attribute ) { + if( !name.empty() && !attribute.empty() ) + m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"'; + return *this; + } + + XmlWriter& XmlWriter::writeAttribute( std::string const& name, const char* attribute ) { + if( !name.empty() && attribute && attribute[0] != '\0' ) + m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"'; + return *this; + } + + XmlWriter& XmlWriter::writeAttribute( std::string const& name, bool attribute ) { + m_os << ' ' << name << "=\"" << ( attribute ? "true" : "false" ) << '"'; + return *this; + } + + XmlWriter& XmlWriter::writeText( std::string const& text, bool indent ) { + if( !text.empty() ){ + bool tagWasOpen = m_tagIsOpen; + ensureTagClosed(); + if( tagWasOpen && indent ) + m_os << m_indent; + m_os << XmlEncode( text ); + m_needsNewline = true; + } + return *this; + } + + //XmlWriter& XmlWriter::writeComment( std::string const& text ) { + // ensureTagClosed(); + // m_os << m_indent << ""; + // m_needsNewline = true; + // return *this; + //} + + //void XmlWriter::writeStylesheetRef( std::string const& url ) { + // m_os << "\n"; + //} + + //XmlWriter& XmlWriter::writeBlankLine() { + // ensureTagClosed(); + // m_os << '\n'; + // return *this; + //} + + void XmlWriter::ensureTagClosed() { + if( m_tagIsOpen ) { + m_os << ">" << std::endl; + m_tagIsOpen = false; + } + } + + void XmlWriter::writeDeclaration() { + m_os << "\n"; + } + + void XmlWriter::newlineIfNecessary() { + if( m_needsNewline ) { + m_os << std::endl; + m_needsNewline = false; + } + } + +// ================================================================================================= +// End of copy-pasted code from Catch +// ================================================================================================= + + // clang-format on + + struct XmlReporter : public IReporter + { + XmlWriter xml; + std::mutex mutex; + + // caching pointers/references to objects of these types - safe to do + const ContextOptions& opt; + const TestCaseData* tc = nullptr; + + XmlReporter(const ContextOptions& co) + : xml(*co.cout) + , opt(co) {} + + void log_contexts() { + int num_contexts = get_num_active_contexts(); + if(num_contexts) { + auto contexts = get_active_contexts(); + std::stringstream ss; + for(int i = 0; i < num_contexts; ++i) { + contexts[i]->stringify(&ss); + xml.scopedElement("Info").writeText(ss.str()); + ss.str(""); + } + } + } + + unsigned line(unsigned l) const { return opt.no_line_numbers ? 0 : l; } + + void test_case_start_impl(const TestCaseData& in) { + bool open_ts_tag = false; + if(tc != nullptr) { // we have already opened a test suite + if(strcmp(tc->m_test_suite, in.m_test_suite) != 0) { + xml.endElement(); + open_ts_tag = true; + } + } + else { + open_ts_tag = true; // first test case ==> first test suite + } + + if(open_ts_tag) { + xml.startElement("TestSuite"); + xml.writeAttribute("name", in.m_test_suite); + } + + tc = ∈ + xml.startElement("TestCase") + .writeAttribute("name", in.m_name) + .writeAttribute("filename", skipPathFromFilename(in.m_file)) + .writeAttribute("line", line(in.m_line)) + .writeAttribute("description", in.m_description); + + if(Approx(in.m_timeout) != 0) + xml.writeAttribute("timeout", in.m_timeout); + if(in.m_may_fail) + xml.writeAttribute("may_fail", true); + if(in.m_should_fail) + xml.writeAttribute("should_fail", true); + } + + // ========================================================================================= + // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE + // ========================================================================================= + + void report_query(const QueryData& in) override { + test_run_start(); + if(opt.list_reporters) { + for(auto& curr : getReporters()) + xml.scopedElement("Reporter") + .writeAttribute("priority", curr.first.first) + .writeAttribute("name", curr.first.second); + } else if(opt.count || opt.list_test_cases) { + for(unsigned i = 0; i < in.num_data; ++i) + xml.scopedElement("TestCase").writeAttribute("name", in.data[i]); + xml.scopedElement("OverallResultsTestCases") + .writeAttribute("unskipped", in.run_stats->numTestCasesPassingFilters); + } else if(opt.list_test_suites) { + for(unsigned i = 0; i < in.num_data; ++i) + xml.scopedElement("TestSuite").writeAttribute("name", in.data[i]); + xml.scopedElement("OverallResultsTestCases") + .writeAttribute("unskipped", in.run_stats->numTestCasesPassingFilters); + xml.scopedElement("OverallResultsTestSuites") + .writeAttribute("unskipped", in.run_stats->numTestSuitesPassingFilters); + } + xml.endElement(); + } + + void test_run_start() override { + // remove .exe extension - mainly to have the same output on UNIX and Windows + std::string binary_name = skipPathFromFilename(opt.binary_name.c_str()); +#ifdef DOCTEST_PLATFORM_WINDOWS + if(binary_name.rfind(".exe") != std::string::npos) + binary_name = binary_name.substr(0, binary_name.length() - 4); +#endif // DOCTEST_PLATFORM_WINDOWS + + xml.startElement("doctest").writeAttribute("binary", binary_name); + if(opt.no_version == false) + xml.writeAttribute("version", DOCTEST_VERSION_STR); + + // only the consequential ones (TODO: filters) + xml.scopedElement("Options") + .writeAttribute("order_by", opt.order_by.c_str()) + .writeAttribute("rand_seed", opt.rand_seed) + .writeAttribute("first", opt.first) + .writeAttribute("last", opt.last) + .writeAttribute("abort_after", opt.abort_after) + .writeAttribute("subcase_filter_levels", opt.subcase_filter_levels) + .writeAttribute("case_sensitive", opt.case_sensitive) + .writeAttribute("no_throw", opt.no_throw) + .writeAttribute("no_skip", opt.no_skip); + } + + void test_run_end(const TestRunStats& p) override { + if(tc) // the TestSuite tag - only if there has been at least 1 test case + xml.endElement(); + + xml.scopedElement("OverallResultsAsserts") + .writeAttribute("successes", p.numAsserts - p.numAssertsFailed) + .writeAttribute("failures", p.numAssertsFailed); + + xml.startElement("OverallResultsTestCases") + .writeAttribute("successes", + p.numTestCasesPassingFilters - p.numTestCasesFailed) + .writeAttribute("failures", p.numTestCasesFailed); + if(opt.no_skipped_summary == false) + xml.writeAttribute("skipped", p.numTestCases - p.numTestCasesPassingFilters); + xml.endElement(); + + xml.endElement(); + } + + void test_case_start(const TestCaseData& in) override { + test_case_start_impl(in); + xml.ensureTagClosed(); + } + + void test_case_end(const CurrentTestCaseStats& st) override { + xml.startElement("OverallResultsAsserts") + .writeAttribute("successes", + st.numAssertsCurrentTest - st.numAssertsFailedCurrentTest) + .writeAttribute("failures", st.numAssertsFailedCurrentTest); + if(opt.duration) + xml.writeAttribute("duration", st.seconds); + if(tc->m_expected_failures) + xml.writeAttribute("expected_failures", tc->m_expected_failures); + xml.endElement(); + + xml.endElement(); + } + + void test_case_exception(const TestCaseException& e) override { + xml.scopedElement("Exception") + .writeAttribute("crash", e.is_crash) + .writeText(e.error_string.c_str()); + } + + void subcase_start(const SubcaseSignature& in) override { + xml.startElement("SubCase") + .writeAttribute("name", in.m_name) + .writeAttribute("filename", skipPathFromFilename(in.m_file)) + .writeAttribute("line", line(in.m_line)); + } + + void subcase_end() override { xml.endElement(); } + + void log_assert(const AssertData& rb) override { + if(!rb.m_failed && !opt.success) + return; + + std::lock_guard lock(mutex); + + xml.startElement("Expression") + .writeAttribute("success", !rb.m_failed) + .writeAttribute("type", assertString(rb.m_at)) + .writeAttribute("filename", skipPathFromFilename(rb.m_file)) + .writeAttribute("line", line(rb.m_line)); + + xml.scopedElement("Original").writeText(rb.m_expr); + + if(rb.m_threw) + xml.scopedElement("Exception").writeText(rb.m_exception.c_str()); + + if(rb.m_at & (assertType::is_throws_as | assertType::is_throws_with)) { + xml.scopedElement("ExpectedException").writeText(rb.m_exception_type); + } else if((rb.m_at & assertType::is_normal) && !rb.m_threw) { + xml.scopedElement("Expanded").writeText(rb.m_decomp.c_str()); + } + + log_contexts(); + + xml.endElement(); + } + + void log_message(const MessageData& mb) override { + std::lock_guard lock(mutex); + + xml.startElement("Message") + .writeAttribute("type", failureString(mb.m_severity)) + .writeAttribute("filename", skipPathFromFilename(mb.m_file)) + .writeAttribute("line", line(mb.m_line)); + + xml.scopedElement("Text").writeText(mb.m_string.c_str()); + + log_contexts(); + + xml.endElement(); + } + + void test_case_skipped(const TestCaseData& in) override { + if(opt.no_skipped_summary == false) { + test_case_start_impl(in); + xml.writeAttribute("skipped", "true"); + xml.endElement(); + } + } + }; + + DOCTEST_REGISTER_REPORTER("xml", 0, XmlReporter); + + struct Whitespace + { + int nrSpaces; + explicit Whitespace(int nr) + : nrSpaces(nr) {} + }; + + std::ostream& operator<<(std::ostream& out, const Whitespace& ws) { + if(ws.nrSpaces != 0) + out << std::setw(ws.nrSpaces) << ' '; + return out; + } + + struct ConsoleReporter : public IReporter + { + std::ostream& s; + bool hasLoggedCurrentTestStart; + std::vector subcasesStack; + std::mutex mutex; + + // caching pointers/references to objects of these types - safe to do + const ContextOptions& opt; + const TestCaseData* tc; + + ConsoleReporter(const ContextOptions& co) + : s(*co.cout) + , opt(co) {} + + ConsoleReporter(const ContextOptions& co, std::ostream& ostr) + : s(ostr) + , opt(co) {} + + // ========================================================================================= + // WHAT FOLLOWS ARE HELPERS USED BY THE OVERRIDES OF THE VIRTUAL METHODS OF THE INTERFACE + // ========================================================================================= + + void separator_to_stream() { + s << Color::Yellow + << "===============================================================================" + "\n"; + } + + const char* getSuccessOrFailString(bool success, assertType::Enum at, + const char* success_str) { + if(success) + return success_str; + return failureString(at); + } + + Color::Enum getSuccessOrFailColor(bool success, assertType::Enum at) { + return success ? Color::BrightGreen : + (at & assertType::is_warn) ? Color::Yellow : Color::Red; + } + + void successOrFailColoredStringToStream(bool success, assertType::Enum at, + const char* success_str = "SUCCESS") { + s << getSuccessOrFailColor(success, at) + << getSuccessOrFailString(success, at, success_str) << ": "; + } + + void log_contexts() { + int num_contexts = get_num_active_contexts(); + if(num_contexts) { + auto contexts = get_active_contexts(); + + s << Color::None << " logged: "; + for(int i = 0; i < num_contexts; ++i) { + s << (i == 0 ? "" : " "); + contexts[i]->stringify(&s); + s << "\n"; + } + } + + s << "\n"; + } + + void logTestStart() { + if(hasLoggedCurrentTestStart) + return; + + separator_to_stream(); + file_line_to_stream(s, tc->m_file, tc->m_line, "\n"); + if(tc->m_description) + s << Color::Yellow << "DESCRIPTION: " << Color::None << tc->m_description << "\n"; + if(tc->m_test_suite && tc->m_test_suite[0] != '\0') + s << Color::Yellow << "TEST SUITE: " << Color::None << tc->m_test_suite << "\n"; + if(strncmp(tc->m_name, " Scenario:", 11) != 0) + s << Color::None << "TEST CASE: "; + s << Color::None << tc->m_name << "\n"; + + for(auto& curr : subcasesStack) + if(curr.m_name[0] != '\0') + s << " " << curr.m_name << "\n"; + + s << "\n"; + + hasLoggedCurrentTestStart = true; + } + + void printVersion() { + if(opt.no_version == false) + s << Color::Cyan << "[doctest] " << Color::None << "doctest version is \"" + << DOCTEST_VERSION_STR << "\"\n"; + } + + void printIntro() { + printVersion(); + s << Color::Cyan << "[doctest] " << Color::None + << "run with \"--" DOCTEST_OPTIONS_PREFIX_DISPLAY "help\" for options\n"; + } + + void printHelp() { + int sizePrefixDisplay = static_cast(strlen(DOCTEST_OPTIONS_PREFIX_DISPLAY)); + printVersion(); + // clang-format off + s << Color::Cyan << "[doctest]\n" << Color::None; + s << Color::Cyan << "[doctest] " << Color::None; + s << "boolean values: \"1/on/yes/true\" or \"0/off/no/false\"\n"; + s << Color::Cyan << "[doctest] " << Color::None; + s << "filter values: \"str1,str2,str3\" (comma separated strings)\n"; + s << Color::Cyan << "[doctest]\n" << Color::None; + s << Color::Cyan << "[doctest] " << Color::None; + s << "filters use wildcards for matching strings\n"; + s << Color::Cyan << "[doctest] " << Color::None; + s << "something passes a filter if any of the strings in a filter matches\n"; +#ifndef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS + s << Color::Cyan << "[doctest]\n" << Color::None; + s << Color::Cyan << "[doctest] " << Color::None; + s << "ALL FLAGS, OPTIONS AND FILTERS ALSO AVAILABLE WITH A \"" DOCTEST_CONFIG_OPTIONS_PREFIX "\" PREFIX!!!\n"; +#endif + s << Color::Cyan << "[doctest]\n" << Color::None; + s << Color::Cyan << "[doctest] " << Color::None; + s << "Query flags - the program quits after them. Available:\n\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "?, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "help, -" DOCTEST_OPTIONS_PREFIX_DISPLAY "h " + << Whitespace(sizePrefixDisplay*0) << "prints this message\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "v, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "version " + << Whitespace(sizePrefixDisplay*1) << "prints the version\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "c, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "count " + << Whitespace(sizePrefixDisplay*1) << "prints the number of matching tests\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ltc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "list-test-cases " + << Whitespace(sizePrefixDisplay*1) << "lists all matching tests by name\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "lts, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "list-test-suites " + << Whitespace(sizePrefixDisplay*1) << "lists all matching test suites\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "lr, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "list-reporters " + << Whitespace(sizePrefixDisplay*1) << "lists all registered reporters\n\n"; + // ================================================================================== << 79 + s << Color::Cyan << "[doctest] " << Color::None; + s << "The available / options/filters are:\n\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "tc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-case= " + << Whitespace(sizePrefixDisplay*1) << "filters tests by their name\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "tce, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-case-exclude= " + << Whitespace(sizePrefixDisplay*1) << "filters OUT tests by their name\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sf, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "source-file= " + << Whitespace(sizePrefixDisplay*1) << "filters tests by their file\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sfe, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "source-file-exclude= " + << Whitespace(sizePrefixDisplay*1) << "filters OUT tests by their file\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ts, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-suite= " + << Whitespace(sizePrefixDisplay*1) << "filters tests by their test suite\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "tse, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-suite-exclude= " + << Whitespace(sizePrefixDisplay*1) << "filters OUT tests by their test suite\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "subcase= " + << Whitespace(sizePrefixDisplay*1) << "filters subcases by their name\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sce, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "subcase-exclude= " + << Whitespace(sizePrefixDisplay*1) << "filters OUT subcases by their name\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "r, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "reporters= " + << Whitespace(sizePrefixDisplay*1) << "reporters to use (console is default)\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "o, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "out= " + << Whitespace(sizePrefixDisplay*1) << "output filename\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ob, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "order-by= " + << Whitespace(sizePrefixDisplay*1) << "how the tests should be ordered\n"; + s << Whitespace(sizePrefixDisplay*3) << " - by [file/suite/name/rand]\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "rs, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "rand-seed= " + << Whitespace(sizePrefixDisplay*1) << "seed for random ordering\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "f, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "first= " + << Whitespace(sizePrefixDisplay*1) << "the first test passing the filters to\n"; + s << Whitespace(sizePrefixDisplay*3) << " execute - for range-based execution\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "l, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "last= " + << Whitespace(sizePrefixDisplay*1) << "the last test passing the filters to\n"; + s << Whitespace(sizePrefixDisplay*3) << " execute - for range-based execution\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "aa, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "abort-after= " + << Whitespace(sizePrefixDisplay*1) << "stop after failed assertions\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "scfl,--" DOCTEST_OPTIONS_PREFIX_DISPLAY "subcase-filter-levels= " + << Whitespace(sizePrefixDisplay*1) << "apply filters for the first levels\n"; + s << Color::Cyan << "\n[doctest] " << Color::None; + s << "Bool options - can be used like flags and true is assumed. Available:\n\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "s, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "success= " + << Whitespace(sizePrefixDisplay*1) << "include successful assertions in output\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "cs, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "case-sensitive= " + << Whitespace(sizePrefixDisplay*1) << "filters being treated as case sensitive\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "e, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "exit= " + << Whitespace(sizePrefixDisplay*1) << "exits after the tests finish\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "d, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "duration= " + << Whitespace(sizePrefixDisplay*1) << "prints the time duration of each test\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nt, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-throw= " + << Whitespace(sizePrefixDisplay*1) << "skips exceptions-related assert checks\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ne, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-exitcode= " + << Whitespace(sizePrefixDisplay*1) << "returns (or exits) always with success\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nr, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-run= " + << Whitespace(sizePrefixDisplay*1) << "skips all runtime doctest operations\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nv, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-version= " + << Whitespace(sizePrefixDisplay*1) << "omit the framework version in the output\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-colors= " + << Whitespace(sizePrefixDisplay*1) << "disables colors in output\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "fc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "force-colors= " + << Whitespace(sizePrefixDisplay*1) << "use colors even when not in a tty\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nb, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-breaks= " + << Whitespace(sizePrefixDisplay*1) << "disables breakpoints in debuggers\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ns, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-skip= " + << Whitespace(sizePrefixDisplay*1) << "don't skip test cases marked as skip\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "gfl, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "gnu-file-line= " + << Whitespace(sizePrefixDisplay*1) << ":n: vs (n): for line numbers in output\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "npf, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-path-filenames= " + << Whitespace(sizePrefixDisplay*1) << "only filenames and no paths in output\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nln, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-line-numbers= " + << Whitespace(sizePrefixDisplay*1) << "0 instead of real line numbers in output\n"; + // ================================================================================== << 79 + // clang-format on + + s << Color::Cyan << "\n[doctest] " << Color::None; + s << "for more information visit the project documentation\n\n"; + } + + void printRegisteredReporters() { + printVersion(); + s << Color::Cyan << "[doctest] " << Color::None << "listing all registered reporters\n"; + for(auto& curr : getReporters()) + s << "priority: " << std::setw(5) << curr.first.first + << " name: " << curr.first.second << "\n"; + } + + void list_query_results() { + separator_to_stream(); + if(opt.count || opt.list_test_cases) { + s << Color::Cyan << "[doctest] " << Color::None + << "unskipped test cases passing the current filters: " + << g_cs->numTestCasesPassingFilters << "\n"; + } else if(opt.list_test_suites) { + s << Color::Cyan << "[doctest] " << Color::None + << "unskipped test cases passing the current filters: " + << g_cs->numTestCasesPassingFilters << "\n"; + s << Color::Cyan << "[doctest] " << Color::None + << "test suites with unskipped test cases passing the current filters: " + << g_cs->numTestSuitesPassingFilters << "\n"; + } + } + + // ========================================================================================= + // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE + // ========================================================================================= + + void report_query(const QueryData& in) override { + if(opt.version) { + printVersion(); + } else if(opt.help) { + printHelp(); + } else if(opt.list_reporters) { + printRegisteredReporters(); + } else if(opt.count || opt.list_test_cases) { + if(opt.list_test_cases) { + s << Color::Cyan << "[doctest] " << Color::None + << "listing all test case names\n"; + separator_to_stream(); + } + + for(unsigned i = 0; i < in.num_data; ++i) + s << Color::None << in.data[i] << "\n"; + + separator_to_stream(); + + s << Color::Cyan << "[doctest] " << Color::None + << "unskipped test cases passing the current filters: " + << g_cs->numTestCasesPassingFilters << "\n"; + + } else if(opt.list_test_suites) { + s << Color::Cyan << "[doctest] " << Color::None << "listing all test suites\n"; + separator_to_stream(); + + for(unsigned i = 0; i < in.num_data; ++i) + s << Color::None << in.data[i] << "\n"; + + separator_to_stream(); + + s << Color::Cyan << "[doctest] " << Color::None + << "unskipped test cases passing the current filters: " + << g_cs->numTestCasesPassingFilters << "\n"; + s << Color::Cyan << "[doctest] " << Color::None + << "test suites with unskipped test cases passing the current filters: " + << g_cs->numTestSuitesPassingFilters << "\n"; + } + } + + void test_run_start() override { printIntro(); } + + void test_run_end(const TestRunStats& p) override { + separator_to_stream(); + + const bool anythingFailed = p.numTestCasesFailed > 0 || p.numAssertsFailed > 0; + s << Color::Cyan << "[doctest] " << Color::None << "test cases: " << std::setw(6) + << p.numTestCasesPassingFilters << " | " + << ((p.numTestCasesPassingFilters == 0 || anythingFailed) ? Color::None : + Color::Green) + << std::setw(6) << p.numTestCasesPassingFilters - p.numTestCasesFailed << " passed" + << Color::None << " | " << (p.numTestCasesFailed > 0 ? Color::Red : Color::None) + << std::setw(6) << p.numTestCasesFailed << " failed" << Color::None << " | "; + if(opt.no_skipped_summary == false) { + const int numSkipped = p.numTestCases - p.numTestCasesPassingFilters; + s << (numSkipped == 0 ? Color::None : Color::Yellow) << std::setw(6) << numSkipped + << " skipped" << Color::None; + } + s << "\n"; + s << Color::Cyan << "[doctest] " << Color::None << "assertions: " << std::setw(6) + << p.numAsserts << " | " + << ((p.numAsserts == 0 || anythingFailed) ? Color::None : Color::Green) + << std::setw(6) << (p.numAsserts - p.numAssertsFailed) << " passed" << Color::None + << " | " << (p.numAssertsFailed > 0 ? Color::Red : Color::None) << std::setw(6) + << p.numAssertsFailed << " failed" << Color::None << " |\n"; + s << Color::Cyan << "[doctest] " << Color::None + << "Status: " << (p.numTestCasesFailed > 0 ? Color::Red : Color::Green) + << ((p.numTestCasesFailed > 0) ? "FAILURE!" : "SUCCESS!") << Color::None << std::endl; + } + + void test_case_start(const TestCaseData& in) override { + hasLoggedCurrentTestStart = false; + tc = ∈ + } + + void test_case_end(const CurrentTestCaseStats& st) override { + // log the preamble of the test case only if there is something + // else to print - something other than that an assert has failed + if(opt.duration || + (st.failure_flags && st.failure_flags != TestCaseFailureReason::AssertFailure)) + logTestStart(); + + if(opt.duration) + s << Color::None << std::setprecision(6) << std::fixed << st.seconds + << " s: " << tc->m_name << "\n"; + + if(st.failure_flags & TestCaseFailureReason::Timeout) + s << Color::Red << "Test case exceeded time limit of " << std::setprecision(6) + << std::fixed << tc->m_timeout << "!\n"; + + if(st.failure_flags & TestCaseFailureReason::ShouldHaveFailedButDidnt) { + s << Color::Red << "Should have failed but didn't! Marking it as failed!\n"; + } else if(st.failure_flags & TestCaseFailureReason::ShouldHaveFailedAndDid) { + s << Color::Yellow << "Failed as expected so marking it as not failed\n"; + } else if(st.failure_flags & TestCaseFailureReason::CouldHaveFailedAndDid) { + s << Color::Yellow << "Allowed to fail so marking it as not failed\n"; + } else if(st.failure_flags & TestCaseFailureReason::DidntFailExactlyNumTimes) { + s << Color::Red << "Didn't fail exactly " << tc->m_expected_failures + << " times so marking it as failed!\n"; + } else if(st.failure_flags & TestCaseFailureReason::FailedExactlyNumTimes) { + s << Color::Yellow << "Failed exactly " << tc->m_expected_failures + << " times as expected so marking it as not failed!\n"; + } + if(st.failure_flags & TestCaseFailureReason::TooManyFailedAsserts) { + s << Color::Red << "Aborting - too many failed asserts!\n"; + } + s << Color::None; + } + + void test_case_exception(const TestCaseException& e) override { + logTestStart(); + + file_line_to_stream(s, tc->m_file, tc->m_line, " "); + successOrFailColoredStringToStream(false, e.is_crash ? assertType::is_require : + assertType::is_check); + s << Color::Red << (e.is_crash ? "test case CRASHED: " : "test case THREW exception: ") + << Color::Cyan << e.error_string << "\n"; + + int num_stringified_contexts = get_num_stringified_contexts(); + if(num_stringified_contexts) { + auto stringified_contexts = get_stringified_contexts(); + s << Color::None << " logged: "; + for(int i = num_stringified_contexts - 1; i >= 0; --i) { + s << (i == num_stringified_contexts - 1 ? "" : " ") + << stringified_contexts[i] << "\n"; + } + } + s << "\n" << Color::None; + } + + void subcase_start(const SubcaseSignature& subc) override { + subcasesStack.push_back(subc); + hasLoggedCurrentTestStart = false; + } + + void subcase_end() override { + subcasesStack.pop_back(); + hasLoggedCurrentTestStart = false; + } + + void log_assert(const AssertData& rb) override { + if(!rb.m_failed && !opt.success) + return; + + std::lock_guard lock(mutex); + + logTestStart(); + + file_line_to_stream(s, rb.m_file, rb.m_line, " "); + successOrFailColoredStringToStream(!rb.m_failed, rb.m_at); + if((rb.m_at & (assertType::is_throws_as | assertType::is_throws_with)) == + 0) //!OCLINT bitwise operator in conditional + s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << " ) " + << Color::None; + + if(rb.m_at & assertType::is_throws) { //!OCLINT bitwise operator in conditional + s << (rb.m_threw ? "threw as expected!" : "did NOT throw at all!") << "\n"; + } else if(rb.m_at & + assertType::is_throws_as) { //!OCLINT bitwise operator in conditional + s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", " + << rb.m_exception_type << " ) " << Color::None + << (rb.m_threw ? (rb.m_threw_as ? "threw as expected!" : + "threw a DIFFERENT exception: ") : + "did NOT throw at all!") + << Color::Cyan << rb.m_exception << "\n"; + } else if(rb.m_at & + assertType::is_throws_with) { //!OCLINT bitwise operator in conditional + s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", \"" + << rb.m_exception_type << "\" ) " << Color::None + << (rb.m_threw ? (!rb.m_failed ? "threw as expected!" : + "threw a DIFFERENT exception: ") : + "did NOT throw at all!") + << Color::Cyan << rb.m_exception << "\n"; + } else if(rb.m_at & assertType::is_nothrow) { //!OCLINT bitwise operator in conditional + s << (rb.m_threw ? "THREW exception: " : "didn't throw!") << Color::Cyan + << rb.m_exception << "\n"; + } else { + s << (rb.m_threw ? "THREW exception: " : + (!rb.m_failed ? "is correct!\n" : "is NOT correct!\n")); + if(rb.m_threw) + s << rb.m_exception << "\n"; + else + s << " values: " << assertString(rb.m_at) << "( " << rb.m_decomp << " )\n"; + } + + log_contexts(); + } + + void log_message(const MessageData& mb) override { + std::lock_guard lock(mutex); + + logTestStart(); + + file_line_to_stream(s, mb.m_file, mb.m_line, " "); + s << getSuccessOrFailColor(false, mb.m_severity) + << getSuccessOrFailString(mb.m_severity & assertType::is_warn, mb.m_severity, + "MESSAGE") << ": "; + s << Color::None << mb.m_string << "\n"; + log_contexts(); + } + + void test_case_skipped(const TestCaseData&) override {} + }; + + DOCTEST_REGISTER_REPORTER("console", 0, ConsoleReporter); + +#ifdef DOCTEST_PLATFORM_WINDOWS + struct DebugOutputWindowReporter : public ConsoleReporter + { + DOCTEST_THREAD_LOCAL static std::ostringstream oss; + + DebugOutputWindowReporter(const ContextOptions& co) + : ConsoleReporter(co, oss) {} + +#define DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(func, type, arg) \ + void func(type arg) override { \ + bool with_col = g_no_colors; \ + g_no_colors = false; \ + ConsoleReporter::func(arg); \ + DOCTEST_OUTPUT_DEBUG_STRING(oss.str().c_str()); \ + oss.str(""); \ + g_no_colors = with_col; \ + } + + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_run_start, DOCTEST_EMPTY, DOCTEST_EMPTY) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_run_end, const TestRunStats&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_start, const TestCaseData&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_end, const CurrentTestCaseStats&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_exception, const TestCaseException&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(subcase_start, const SubcaseSignature&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(subcase_end, DOCTEST_EMPTY, DOCTEST_EMPTY) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(log_assert, const AssertData&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(log_message, const MessageData&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_skipped, const TestCaseData&, in) + }; + + DOCTEST_THREAD_LOCAL std::ostringstream DebugOutputWindowReporter::oss; +#endif // DOCTEST_PLATFORM_WINDOWS + + // the implementation of parseFlag() + bool parseFlagImpl(int argc, const char* const* argv, const char* pattern) { + for(int i = argc - 1; i >= 0; --i) { + auto temp = std::strstr(argv[i], pattern); + if(temp && strlen(temp) == strlen(pattern)) { + // eliminate strings in which the chars before the option are not '-' + bool noBadCharsFound = true; //!OCLINT prefer early exits and continue + while(temp != argv[i]) { + if(*--temp != '-') { + noBadCharsFound = false; + break; + } + } + if(noBadCharsFound && argv[i][0] == '-') + return true; + } + } + return false; + } + + // locates a flag on the command line + bool parseFlag(int argc, const char* const* argv, const char* pattern) { +#ifndef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS + // offset (normally 3 for "dt-") to skip prefix + if(parseFlagImpl(argc, argv, pattern + strlen(DOCTEST_CONFIG_OPTIONS_PREFIX))) + return true; +#endif // DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS + return parseFlagImpl(argc, argv, pattern); + } + + // the implementation of parseOption() + bool parseOptionImpl(int argc, const char* const* argv, const char* pattern, String& res) { + for(int i = argc - 1; i >= 0; --i) { + auto temp = std::strstr(argv[i], pattern); + if(temp) { //!OCLINT prefer early exits and continue + // eliminate matches in which the chars before the option are not '-' + bool noBadCharsFound = true; + auto curr = argv[i]; + while(curr != temp) { + if(*curr++ != '-') { + noBadCharsFound = false; + break; + } + } + if(noBadCharsFound && argv[i][0] == '-') { + temp += strlen(pattern); + const unsigned len = strlen(temp); + if(len) { + res = temp; + return true; + } + } + } + } + return false; + } + + // parses an option and returns the string after the '=' character + bool parseOption(int argc, const char* const* argv, const char* pattern, String& res, + const String& defaultVal = String()) { + res = defaultVal; +#ifndef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS + // offset (normally 3 for "dt-") to skip prefix + if(parseOptionImpl(argc, argv, pattern + strlen(DOCTEST_CONFIG_OPTIONS_PREFIX), res)) + return true; +#endif // DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS + return parseOptionImpl(argc, argv, pattern, res); + } + + // parses a comma separated list of words after a pattern in one of the arguments in argv + bool parseCommaSepArgs(int argc, const char* const* argv, const char* pattern, + std::vector& res) { + String filtersString; + if(parseOption(argc, argv, pattern, filtersString)) { + // tokenize with "," as a separator + // cppcheck-suppress strtokCalled + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") + auto pch = std::strtok(filtersString.c_str(), ","); // modifies the string + while(pch != nullptr) { + if(strlen(pch)) + res.push_back(pch); + // uses the strtok() internal state to go to the next token + // cppcheck-suppress strtokCalled + pch = std::strtok(nullptr, ","); + } + DOCTEST_CLANG_SUPPRESS_WARNING_POP + return true; + } + return false; + } + + enum optionType + { + option_bool, + option_int + }; + + // parses an int/bool option from the command line + bool parseIntOption(int argc, const char* const* argv, const char* pattern, optionType type, + int& res) { + String parsedValue; + if(!parseOption(argc, argv, pattern, parsedValue)) + return false; + + if(type == 0) { + // boolean + const char positive[][5] = {"1", "true", "on", "yes"}; // 5 - strlen("true") + 1 + const char negative[][6] = {"0", "false", "off", "no"}; // 6 - strlen("false") + 1 + + // if the value matches any of the positive/negative possibilities + for(unsigned i = 0; i < 4; i++) { + if(parsedValue.compare(positive[i], true) == 0) { + res = 1; //!OCLINT parameter reassignment + return true; + } + if(parsedValue.compare(negative[i], true) == 0) { + res = 0; //!OCLINT parameter reassignment + return true; + } + } + } else { + // integer + // TODO: change this to use std::stoi or something else! currently it uses undefined behavior - assumes '0' on failed parse... + int theInt = std::atoi(parsedValue.c_str()); // NOLINT + if(theInt != 0) { + res = theInt; //!OCLINT parameter reassignment + return true; + } + } + return false; + } +} // namespace + +Context::Context(int argc, const char* const* argv) + : p(new detail::ContextState) { + parseArgs(argc, argv, true); + if(argc) + p->binary_name = argv[0]; +} + +Context::~Context() { + if(g_cs == p) + g_cs = nullptr; + delete p; +} + +void Context::applyCommandLine(int argc, const char* const* argv) { + parseArgs(argc, argv); + if(argc) + p->binary_name = argv[0]; +} + +// parses args +void Context::parseArgs(int argc, const char* const* argv, bool withDefaults) { + using namespace detail; + + // clang-format off + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "source-file=", p->filters[0]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sf=", p->filters[0]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "source-file-exclude=",p->filters[1]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sfe=", p->filters[1]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-suite=", p->filters[2]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "ts=", p->filters[2]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-suite-exclude=", p->filters[3]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "tse=", p->filters[3]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-case=", p->filters[4]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "tc=", p->filters[4]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-case-exclude=", p->filters[5]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "tce=", p->filters[5]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "subcase=", p->filters[6]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sc=", p->filters[6]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "subcase-exclude=", p->filters[7]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sce=", p->filters[7]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "reporters=", p->filters[8]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "r=", p->filters[8]); + // clang-format on + + int intRes = 0; + String strRes; + +#define DOCTEST_PARSE_AS_BOOL_OR_FLAG(name, sname, var, default) \ + if(parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name "=", option_bool, intRes) || \ + parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname "=", option_bool, intRes)) \ + p->var = !!intRes; \ + else if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name) || \ + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname)) \ + p->var = true; \ + else if(withDefaults) \ + p->var = default + +#define DOCTEST_PARSE_INT_OPTION(name, sname, var, default) \ + if(parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name "=", option_int, intRes) || \ + parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname "=", option_int, intRes)) \ + p->var = intRes; \ + else if(withDefaults) \ + p->var = default + +#define DOCTEST_PARSE_STR_OPTION(name, sname, var, default) \ + if(parseOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name "=", strRes, default) || \ + parseOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname "=", strRes, default) || \ + withDefaults) \ + p->var = strRes + + // clang-format off + DOCTEST_PARSE_STR_OPTION("out", "o", out, ""); + DOCTEST_PARSE_STR_OPTION("order-by", "ob", order_by, "file"); + DOCTEST_PARSE_INT_OPTION("rand-seed", "rs", rand_seed, 0); + + DOCTEST_PARSE_INT_OPTION("first", "f", first, 0); + DOCTEST_PARSE_INT_OPTION("last", "l", last, UINT_MAX); + + DOCTEST_PARSE_INT_OPTION("abort-after", "aa", abort_after, 0); + DOCTEST_PARSE_INT_OPTION("subcase-filter-levels", "scfl", subcase_filter_levels, INT_MAX); + + DOCTEST_PARSE_AS_BOOL_OR_FLAG("success", "s", success, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("case-sensitive", "cs", case_sensitive, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("exit", "e", exit, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("duration", "d", duration, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-throw", "nt", no_throw, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-exitcode", "ne", no_exitcode, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-run", "nr", no_run, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-version", "nv", no_version, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-colors", "nc", no_colors, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("force-colors", "fc", force_colors, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-breaks", "nb", no_breaks, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-skip", "ns", no_skip, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("gnu-file-line", "gfl", gnu_file_line, !bool(DOCTEST_MSVC)); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-path-filenames", "npf", no_path_in_filenames, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-line-numbers", "nln", no_line_numbers, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-skipped-summary", "nss", no_skipped_summary, false); + // clang-format on + + if(withDefaults) { + p->help = false; + p->version = false; + p->count = false; + p->list_test_cases = false; + p->list_test_suites = false; + p->list_reporters = false; + } + if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "help") || + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "h") || + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "?")) { + p->help = true; + p->exit = true; + } + if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "version") || + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "v")) { + p->version = true; + p->exit = true; + } + if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "count") || + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "c")) { + p->count = true; + p->exit = true; + } + if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "list-test-cases") || + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "ltc")) { + p->list_test_cases = true; + p->exit = true; + } + if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "list-test-suites") || + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "lts")) { + p->list_test_suites = true; + p->exit = true; + } + if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "list-reporters") || + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "lr")) { + p->list_reporters = true; + p->exit = true; + } +} + +// allows the user to add procedurally to the filters from the command line +void Context::addFilter(const char* filter, const char* value) { setOption(filter, value); } + +// allows the user to clear all filters from the command line +void Context::clearFilters() { + for(auto& curr : p->filters) + curr.clear(); +} + +// allows the user to override procedurally the int/bool options from the command line +void Context::setOption(const char* option, int value) { + setOption(option, toString(value).c_str()); +} + +// allows the user to override procedurally the string options from the command line +void Context::setOption(const char* option, const char* value) { + auto argv = String("-") + option + "=" + value; + auto lvalue = argv.c_str(); + parseArgs(1, &lvalue); +} + +// users should query this in their main() and exit the program if true +bool Context::shouldExit() { return p->exit; } + +void Context::setAsDefaultForAssertsOutOfTestCases() { g_cs = p; } + +void Context::setAssertHandler(detail::assert_handler ah) { p->ah = ah; } + +// the main function that does all the filtering and test running +int Context::run() { + using namespace detail; + + // save the old context state in case such was setup - for using asserts out of a testing context + auto old_cs = g_cs; + // this is the current contest + g_cs = p; + is_running_in_test = true; + + g_no_colors = p->no_colors; + p->resetRunData(); + + // stdout by default + p->cout = &std::cout; + p->cerr = &std::cerr; + + // or to a file if specified + std::fstream fstr; + if(p->out.size()) { + fstr.open(p->out.c_str(), std::fstream::out); + p->cout = &fstr; + } + + auto cleanup_and_return = [&]() { + if(fstr.is_open()) + fstr.close(); + + // restore context + g_cs = old_cs; + is_running_in_test = false; + + // we have to free the reporters which were allocated when the run started + for(auto& curr : p->reporters_currently_used) + delete curr; + p->reporters_currently_used.clear(); + + if(p->numTestCasesFailed && !p->no_exitcode) + return EXIT_FAILURE; + return EXIT_SUCCESS; + }; + + DOCTEST_REGISTER_REPORTER("console", 0, ConsoleReporter); + + // setup default reporter if none is given through the command line + if(p->filters[8].empty()) + p->filters[8].push_back("console"); + + // check to see if any of the registered reporters has been selected + for(auto& curr : getReporters()) { + if(matchesAny(curr.first.second.c_str(), p->filters[8], false, p->case_sensitive)) + p->reporters_currently_used.push_back(curr.second(*g_cs)); + } + +#ifdef DOCTEST_PLATFORM_WINDOWS + if(isDebuggerActive()) + p->reporters_currently_used.push_back(new DebugOutputWindowReporter(*g_cs)); +#endif // DOCTEST_PLATFORM_WINDOWS + + // handle version, help and no_run + if(p->no_run || p->version || p->help || p->list_reporters) { + DOCTEST_ITERATE_THROUGH_REPORTERS(report_query, QueryData()); + + return cleanup_and_return(); + } + + std::vector testArray; + for(auto& curr : getRegisteredTests()) + testArray.push_back(&curr); + p->numTestCases = testArray.size(); + + // sort the collected records + if(!testArray.empty()) { + if(p->order_by.compare("file", true) == 0) { + std::sort(testArray.begin(), testArray.end(), fileOrderComparator); + } else if(p->order_by.compare("suite", true) == 0) { + std::sort(testArray.begin(), testArray.end(), suiteOrderComparator); + } else if(p->order_by.compare("name", true) == 0) { + std::sort(testArray.begin(), testArray.end(), nameOrderComparator); + } else if(p->order_by.compare("rand", true) == 0) { + std::srand(p->rand_seed); + + // random_shuffle implementation + const auto first = &testArray[0]; + for(size_t i = testArray.size() - 1; i > 0; --i) { + int idxToSwap = std::rand() % (i + 1); // NOLINT + + const auto temp = first[i]; + + first[i] = first[idxToSwap]; + first[idxToSwap] = temp; + } + } + } + + std::set testSuitesPassingFilt; + + bool query_mode = p->count || p->list_test_cases || p->list_test_suites; + std::vector queryResults; + + if(!query_mode) + DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_start, DOCTEST_EMPTY); + + // invoke the registered functions if they match the filter criteria (or just count them) + for(auto& curr : testArray) { + const auto& tc = *curr; + + bool skip_me = false; + if(tc.m_skip && !p->no_skip) + skip_me = true; + + if(!matchesAny(tc.m_file, p->filters[0], true, p->case_sensitive)) + skip_me = true; + if(matchesAny(tc.m_file, p->filters[1], false, p->case_sensitive)) + skip_me = true; + if(!matchesAny(tc.m_test_suite, p->filters[2], true, p->case_sensitive)) + skip_me = true; + if(matchesAny(tc.m_test_suite, p->filters[3], false, p->case_sensitive)) + skip_me = true; + if(!matchesAny(tc.m_name, p->filters[4], true, p->case_sensitive)) + skip_me = true; + if(matchesAny(tc.m_name, p->filters[5], false, p->case_sensitive)) + skip_me = true; + + if(!skip_me) + p->numTestCasesPassingFilters++; + + // skip the test if it is not in the execution range + if((p->last < p->numTestCasesPassingFilters && p->first <= p->last) || + (p->first > p->numTestCasesPassingFilters)) + skip_me = true; + + if(skip_me) { + if(!query_mode) + DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_skipped, tc); + continue; + } + + // do not execute the test if we are to only count the number of filter passing tests + if(p->count) + continue; + + // print the name of the test and don't execute it + if(p->list_test_cases) { + queryResults.push_back(tc.m_name); + continue; + } + + // print the name of the test suite if not done already and don't execute it + if(p->list_test_suites) { + if((testSuitesPassingFilt.count(tc.m_test_suite) == 0) && tc.m_test_suite[0] != '\0') { + queryResults.push_back(tc.m_test_suite); + testSuitesPassingFilt.insert(tc.m_test_suite); + p->numTestSuitesPassingFilters++; + } + continue; + } + + // execute the test if it passes all the filtering + { + p->currentTest = &tc; + + p->failure_flags = TestCaseFailureReason::None; + p->seconds = 0; + + // reset atomic counters + p->numAssertsFailedCurrentTest_atomic = 0; + p->numAssertsCurrentTest_atomic = 0; + + p->subcasesPassed.clear(); + + DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_start, tc); + + p->timer.start(); + + do { + // reset some of the fields for subcases (except for the set of fully passed ones) + p->should_reenter = false; + p->subcasesCurrentLevel = 0; + p->subcasesEnteredLevels.clear(); + + // reset stuff for logging with INFO() + p->stringifiedContexts.clear(); + +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + try { +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + FatalConditionHandler fatalConditionHandler; // Handle signals + // execute the test + tc.m_test(); + fatalConditionHandler.reset(); +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + } catch(const TestFailureException&) { + p->failure_flags |= TestCaseFailureReason::AssertFailure; + } catch(...) { + DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_exception, + {translateActiveException(), false}); + p->failure_flags |= TestCaseFailureReason::Exception; + } +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + + // exit this loop if enough assertions have failed - even if there are more subcases + if(p->abort_after > 0 && + p->numAssertsFailed + p->numAssertsFailedCurrentTest_atomic >= p->abort_after) { + p->should_reenter = false; + p->failure_flags |= TestCaseFailureReason::TooManyFailedAsserts; + } + } while(p->should_reenter == true); + + p->finalizeTestCaseData(); + + DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_end, *g_cs); + + p->currentTest = nullptr; + + // stop executing tests if enough assertions have failed + if(p->abort_after > 0 && p->numAssertsFailed >= p->abort_after) + break; + } + } + + if(!query_mode) { + DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_end, *g_cs); + } else { + QueryData qdata; + qdata.run_stats = g_cs; + qdata.data = queryResults.data(); + qdata.num_data = unsigned(queryResults.size()); + DOCTEST_ITERATE_THROUGH_REPORTERS(report_query, qdata); + } + + return cleanup_and_return(); +} + +DOCTEST_DEFINE_DEFAULTS(CurrentTestCaseStats); + +DOCTEST_DEFINE_DEFAULTS(TestRunStats); + +IReporter::~IReporter() = default; + +int IReporter::get_num_active_contexts() { return detail::g_infoContexts.size(); } +const IContextScope* const* IReporter::get_active_contexts() { + return get_num_active_contexts() ? &detail::g_infoContexts[0] : nullptr; +} + +int IReporter::get_num_stringified_contexts() { return detail::g_cs->stringifiedContexts.size(); } +const String* IReporter::get_stringified_contexts() { + return get_num_stringified_contexts() ? &detail::g_cs->stringifiedContexts[0] : nullptr; +} + +namespace detail { + void registerReporterImpl(const char* name, int priority, reporterCreatorFunc c) { + getReporters().insert(reporterMap::value_type(reporterMap::key_type(priority, name), c)); + } +} // namespace detail + +// see these issues on the reasoning for this: +// - https://github.com/onqtam/doctest/issues/143#issuecomment-414418903 +// - https://github.com/onqtam/doctest/issues/126 +void DOCTEST_FIX_FOR_MACOS_LIBCPP_IOSFWD_STRING_LINK_ERRORS() { std::cout << std::string(); } + +} // namespace doctest + +#endif // DOCTEST_CONFIG_DISABLE + +#ifdef DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN +DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4007) // 'function' : must be 'attribute' - see issue #182 +int main(int argc, char** argv) { return doctest::Context(argc, argv).run(); } +DOCTEST_MSVC_SUPPRESS_WARNING_POP +#endif // DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN + +DOCTEST_CLANG_SUPPRESS_WARNING_POP +DOCTEST_MSVC_SUPPRESS_WARNING_POP +DOCTEST_GCC_SUPPRESS_WARNING_POP + +#endif // DOCTEST_LIBRARY_IMPLEMENTATION +#endif // DOCTEST_CONFIG_IMPLEMENT diff --git a/lib/ArduinoStreamUtils/extras/test/main.cpp b/lib/ArduinoStreamUtils/extras/test/main.cpp new file mode 100644 index 00000000..dcfcaba1 --- /dev/null +++ b/lib/ArduinoStreamUtils/extras/test/main.cpp @@ -0,0 +1,6 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN +#include "doctest.h" diff --git a/lib/ArduinoStreamUtils/keywords.txt b/lib/ArduinoStreamUtils/keywords.txt new file mode 100644 index 00000000..9f91ccef --- /dev/null +++ b/lib/ArduinoStreamUtils/keywords.txt @@ -0,0 +1,13 @@ +BufferingPrint KEYWORD1 +LoggingClient KEYWORD1 +LoggingPrint KEYWORD1 +LoggingStream KEYWORD1 +ReadBufferingClient KEYWORD1 +ReadBufferingStream KEYWORD1 +ReadLoggingClient KEYWORD1 +ReadLoggingStream KEYWORD1 +ReadThrottlingStream KEYWORD1 +WriteBufferingClient KEYWORD1 +WriteBufferingStream KEYWORD1 +WriteLoggingClient KEYWORD1 +WriteLoggingStream KEYWORD1 \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/library.properties b/lib/ArduinoStreamUtils/library.properties new file mode 100644 index 00000000..00cb5887 --- /dev/null +++ b/lib/ArduinoStreamUtils/library.properties @@ -0,0 +1,11 @@ +name=StreamUtils +version=1.6.1 +author=Benoit Blanchon +maintainer=Benoit Blanchon +sentence=💪 Power-ups for Arduino streams +paragraph=Enhances existing streams with logging, buffering, error correction, and more! Works with Serial, SoftwareSerial, WiFiClient... +category=Other +url=https://github.com/bblanchon/ArduinoStreamUtils +architectures=* +repository=https://github.com/bblanchon/ArduinoStreamUtils.git +license=MIT diff --git a/lib/ArduinoStreamUtils/src/StreamUtils.h b/lib/ArduinoStreamUtils/src/StreamUtils.h new file mode 100644 index 00000000..39637e3a --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils.h @@ -0,0 +1,7 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#include "StreamUtils.hpp" + +using namespace StreamUtils; \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/src/StreamUtils.hpp b/lib/ArduinoStreamUtils/src/StreamUtils.hpp new file mode 100644 index 00000000..f24f78e9 --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils.hpp @@ -0,0 +1,28 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#include "StreamUtils/Clients/HammingClient.hpp" +#include "StreamUtils/Clients/HammingDecodingClient.hpp" +#include "StreamUtils/Clients/HammingEncodingClient.hpp" +#include "StreamUtils/Clients/LoggingClient.hpp" +#include "StreamUtils/Clients/ReadBufferingClient.hpp" +#include "StreamUtils/Clients/ReadLoggingClient.hpp" +#include "StreamUtils/Clients/WriteBufferingClient.hpp" +#include "StreamUtils/Clients/WriteLoggingClient.hpp" +#include "StreamUtils/Prints/BufferingPrint.hpp" +#include "StreamUtils/Prints/HammingPrint.hpp" +#include "StreamUtils/Prints/LoggingPrint.hpp" +#include "StreamUtils/Prints/StringPrint.hpp" +#include "StreamUtils/Streams/EepromStream.hpp" +#include "StreamUtils/Streams/HammingDecodingStream.hpp" +#include "StreamUtils/Streams/HammingEncodingStream.hpp" +#include "StreamUtils/Streams/HammingStream.hpp" +#include "StreamUtils/Streams/LoggingStream.hpp" +#include "StreamUtils/Streams/ReadBufferingStream.hpp" +#include "StreamUtils/Streams/ReadLoggingStream.hpp" +#include "StreamUtils/Streams/ReadThrottlingStream.hpp" +#include "StreamUtils/Streams/StringStream.hpp" +#include "StreamUtils/Streams/WriteBufferingStream.hpp" +#include "StreamUtils/Streams/WriteLoggingStream.hpp" +#include "StreamUtils/Streams/WriteWaitingStream.hpp" diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Buffers/CharArray.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Buffers/CharArray.hpp new file mode 100644 index 00000000..c16f4548 --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Buffers/CharArray.hpp @@ -0,0 +1,57 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include +#include // size_t +#include // memcpy + +namespace StreamUtils { + +template +class CharArray { + public: + CharArray(size_t size, TAllocator allocator = TAllocator()) + : _allocator(allocator) { + _data = reinterpret_cast(_allocator.allocate(size)); + _size = _data ? size : 0; + } + + CharArray(const CharArray &src) : CharArray(src._size, src._allocator) { + if (_data != nullptr) + memcpy(_data, src._data, _size); + } + + ~CharArray() { + _allocator.deallocate(_data); + } + + size_t size() const { + return _size; + } + + operator bool() const { + return _size > 0; + } + + char *operator&() { + return _data; + } + + char &operator[](size_t i) { + return _data[i]; + } + + char operator[](size_t i) const { + return _data[i]; + } + + protected: + TAllocator _allocator; + char *_data; + size_t _size; +}; + +} // namespace StreamUtils \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Buffers/CircularBuffer.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Buffers/CircularBuffer.hpp new file mode 100644 index 00000000..c2f210af --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Buffers/CircularBuffer.hpp @@ -0,0 +1,101 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include + +#include "CharArray.hpp" + +namespace StreamUtils { + +template +class CircularBuffer { + public: + CircularBuffer(size_t capacity, TAllocator allocator = TAllocator()) + : _data(capacity, allocator) { + _begin = _end = _size = 0; + } + + CircularBuffer(const CircularBuffer &src) + : CircularBuffer(src._data.size(), src._allocator) { + if (_data) { + _begin = src._begin; + _end = src._end; + _size = src._size; + } + } + + size_t available() const { + return _size; + } + + size_t capacity() const { + return _data.size(); + } + + void clear() { + _begin = _end = _size = 0; + } + + bool isEmpty() const { + return _size == 0; + } + + bool isFull() const { + return _size == _data.size(); + } + + char peek() const { + assert(_size > 0); + return _data[_begin]; + } + + char read() { + assert(_size > 0); + char result = _data[_begin]; + _begin = (_begin + 1) % _data.size(); + _size--; + return result; + } + + size_t readBytes(char *data, size_t size) { + // don't read more that available + if (size > _size) + size = _size; + + for (size_t i = 0; i < size; i++) + data[i] = read(); + + return size; + } + + size_t write(uint8_t data) { + assert(_size < _data.size()); + _data[_end] = data; + _end = (_end + 1) % _data.size(); + _size++; + return 1; + } + + size_t write(const uint8_t *data, size_t size) { + // don't read more that available + size_t roomLeft = _data.size() - _size; + if (size > roomLeft) + size = roomLeft; + + for (size_t i = 0; i < size; i++) + write(data[i]); + + return size; + } + + private: + CharArray _data; + size_t _size; + size_t _begin; + size_t _end; +}; + +} // namespace StreamUtils \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Buffers/LinearBuffer.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Buffers/LinearBuffer.hpp new file mode 100644 index 00000000..10c108f6 --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Buffers/LinearBuffer.hpp @@ -0,0 +1,111 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include +#include + +#include "../Helpers.hpp" +#include "CharArray.hpp" + +namespace StreamUtils { + +template +class LinearBuffer { + public: + LinearBuffer(size_t capacity, TAllocator allocator = TAllocator()) + : _data(capacity, allocator) { + _begin = _data ? &_data : nullptr; + _end = _begin; + } + + LinearBuffer(const LinearBuffer &src) : _data(src._data) { + if (_data) { + memcpy(&_data, src._begin, src.available()); + _begin = &_data; + _end = &_data + src.available(); + } else { + _begin = nullptr; + _end = nullptr; + } + } + + size_t available() const { + return _end - _begin; + } + + size_t capacity() const { + return _data.size(); + } + + void clear() { + _begin = _end = _data; + } + + bool isEmpty() const { + return available() == 0; + } + + bool isFull() const { + return available() == capacity(); + } + + operator bool() const { + return _data; + } + + char peek() const { + return *_begin; + } + + char read() { + return *_begin++; + } + + size_t readBytes(char *dstPtr, size_t dstSize) { + size_t srcSize = available(); + size_t n = srcSize < dstSize ? srcSize : dstSize; + memcpy(dstPtr, _begin, n); + _begin += n; + return n; + } + + size_t write(uint8_t data) { + assert(!isFull()); + *_end++ = data; + return 1; + } + + size_t write(const uint8_t *data, size_t size) { + size_t roomLeft = capacity() - available(); + if (size > roomLeft) + size = roomLeft; + memcpy(_end, data, size); + _end += size; + return size; + } + + template // Stream or Client + void reloadFrom(TTarget &source, size_t size) { + if (size > _data.size()) + size = _data.size(); + size_t n = readOrReadBytes(source, &_data, size); + _begin = &_data; + _end = &_data + n; + } + + void flushInto(Print &destination) { + if (_begin != _end) + destination.write(_begin, _end - _begin); + _begin = _end = &_data; + } + + private: + CharArray _data; + char *_begin; + char *_end; +}; + +} // namespace StreamUtils \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Clients/ClientProxy.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Clients/ClientProxy.hpp new file mode 100644 index 00000000..23a9cc09 --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Clients/ClientProxy.hpp @@ -0,0 +1,105 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include + +#include "../Configuration.hpp" +#include "../Streams/StreamProxy.hpp" + +namespace StreamUtils { + +template +class ClientProxy : public Client { + public: + explicit ClientProxy(Client &upstream, ReadPolicy reader = ReadPolicy(), + WritePolicy writer = WritePolicy(), + ConnectPolicy connection = ConnectPolicy()) + : _target(upstream), + _reader(reader), + _writer(Polyfills::move(writer)), + _connection(connection) {} + + ClientProxy(const ClientProxy &other) + : _target(other._target), + _reader(other._reader), + _writer(other._writer), + _connection(other._connection) {} + + ~ClientProxy() { + _writer.implicitFlush(_target); + } + + // --- Print --- + + size_t write(const uint8_t *buffer, size_t size) override { + return _writer.write(_target, buffer, size); + } + + size_t write(uint8_t data) override { + return _writer.write(_target, data); + } + + using Print::write; + + // --- Stream --- + + int available() override { + return _reader.available(_target); + } + + int read() override { + return _reader.read(_target); + } + + int peek() override { + return _reader.peek(_target); + } + +#if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL + size_t readBytes(char *buffer, size_t size) override { + return _reader.readBytes(_target, buffer, size); + } +#endif + + // --- Client --- + + int connect(IPAddress ip, uint16_t port) override { + return _connection.connect(_target, ip, port); + } + + int connect(const char *ip, uint16_t port) override { + return _connection.connect(_target, ip, port); + } + + uint8_t connected() override { + return _connection.connected(_target); + } + + void stop() override { + _writer.implicitFlush(_target); + _connection.stop(_target); + } + + operator bool() override { + return _connection.operator_bool(_target); + } + + int read(uint8_t *buf, size_t size) override { + return _reader.read(_target, buf, size); + } + + void flush() override { + _writer.flush(_target); + } + + protected: + Client &_target; + ReadPolicy _reader; + WritePolicy _writer; + ConnectPolicy _connection; +}; + +} // namespace StreamUtils \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Clients/HammingClient.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Clients/HammingClient.hpp new file mode 100644 index 00000000..99442397 --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Clients/HammingClient.hpp @@ -0,0 +1,23 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include "../Policies/ConnectForwardingPolicy.hpp" +#include "../Policies/HammingDecodingPolicy.hpp" +#include "../Policies/HammingEncodingPolicy.hpp" +#include "../Ports/DefaultAllocator.hpp" +#include "ClientProxy.hpp" + +namespace StreamUtils { + +template +using BasicHammingClient = ClientProxy, + HammingEncodingPolicy, + ConnectForwardingPolicy>; + +template +using HammingClient = BasicHammingClient; + +} // namespace StreamUtils diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Clients/HammingDecodingClient.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Clients/HammingDecodingClient.hpp new file mode 100644 index 00000000..ab4de20a --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Clients/HammingDecodingClient.hpp @@ -0,0 +1,24 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include "../Policies/ConnectForwardingPolicy.hpp" +#include "../Policies/HammingDecodingPolicy.hpp" +#include "../Policies/WriteForwardingPolicy.hpp" +#include "../Ports/DefaultAllocator.hpp" +#include "ClientProxy.hpp" + +namespace StreamUtils { + +template +using BasicHammingDecodingClient = + ClientProxy, WriteForwardingPolicy, + ConnectForwardingPolicy>; + +template +using HammingDecodingClient = + BasicHammingDecodingClient; + +} // namespace StreamUtils diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Clients/HammingEncodingClient.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Clients/HammingEncodingClient.hpp new file mode 100644 index 00000000..d341b060 --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Clients/HammingEncodingClient.hpp @@ -0,0 +1,24 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include "../Policies/ConnectForwardingPolicy.hpp" +#include "../Policies/HammingEncodingPolicy.hpp" +#include "../Policies/ReadForwardingPolicy.hpp" +#include "../Ports/DefaultAllocator.hpp" +#include "ClientProxy.hpp" + +namespace StreamUtils { + +template +using BasicHammingEncodingClient = + ClientProxy, + ConnectForwardingPolicy>; + +template +using HammingEncodingClient = + BasicHammingEncodingClient; + +} // namespace StreamUtils diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Clients/LoggingClient.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Clients/LoggingClient.hpp new file mode 100644 index 00000000..c3c8bcbe --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Clients/LoggingClient.hpp @@ -0,0 +1,23 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include "../Policies/ConnectForwardingPolicy.hpp" +#include "../Policies/ReadLoggingPolicy.hpp" +#include "../Policies/WriteLoggingPolicy.hpp" +#include "ClientProxy.hpp" + +namespace StreamUtils { + +struct LoggingClient : ClientProxy { + LoggingClient(Client &target, Print &log) + : ClientProxy(target, ReadLoggingPolicy{log}, + WriteLoggingPolicy{log}, + ConnectForwardingPolicy{}) {} +}; + +} // namespace StreamUtils \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Clients/MemoryClient.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Clients/MemoryClient.hpp new file mode 100644 index 00000000..2e2447dd --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Clients/MemoryClient.hpp @@ -0,0 +1,92 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include + +#include "../Buffers/CircularBuffer.hpp" +#include "../Configuration.hpp" +#include "../Ports/DefaultAllocator.hpp" +#include "../Streams/MemoryStream.hpp" + +namespace StreamUtils { + +template +class BasicMemoryClient : public Client { + public: + BasicMemoryClient(size_t capacity, TAllocator allocator = TAllocator()) + : _stream(capacity, allocator), _connected(false) {} + + BasicMemoryClient(const BasicMemoryClient &src) : _stream(src._stream) {} + + // --- Print --- + + size_t write(uint8_t data) override { + return _stream.write(data); + } + + size_t write(const uint8_t *data, size_t size) override { + return _stream.write(data, size); + } + + // --- Stream --- + + int available() override { + return _stream.available(); + } + + int peek() override { + return _stream.peek(); + } + + int read() override { + return _stream.read(); + } + +#if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL + size_t readBytes(char *data, size_t size) override { + return _stream.readBytes(data, size); + } +#endif + + void flush() override { + _stream.flush(); + } + + // --- Client --- + + int connect(IPAddress, uint16_t) override { + _connected = true; + return 1; + } + + int connect(const char *, uint16_t) override { + _connected = true; + return 1; + } + + uint8_t connected() override { + return _connected; + } + + void stop() override { + _connected = false; + } + + operator bool() override { + return true; + } + + int read(uint8_t *buf, size_t size) override { + return _stream.readBytes(reinterpret_cast(buf), size); + } + + private: + BasicMemoryStream _stream; + bool _connected; +}; +using MemoryClient = BasicMemoryClient; + +} // namespace StreamUtils \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Clients/ReadBufferingClient.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Clients/ReadBufferingClient.hpp new file mode 100644 index 00000000..b3b864eb --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Clients/ReadBufferingClient.hpp @@ -0,0 +1,30 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include "../Policies/ConnectForwardingPolicy.hpp" +#include "../Policies/ReadBufferingPolicy.hpp" +#include "../Policies/WriteForwardingPolicy.hpp" +#include "../Ports/DefaultAllocator.hpp" +#include "ClientProxy.hpp" + +namespace StreamUtils { + +template +class BasicReadBufferingClient + : public ClientProxy, WriteForwardingPolicy, + ConnectForwardingPolicy> { + using base_type = ClientProxy, + WriteForwardingPolicy, ConnectForwardingPolicy>; + + public: + explicit BasicReadBufferingClient(Client &target, size_t capacity, + TAllocator allocator = TAllocator()) + : base_type(target, ReadBufferingPolicy{capacity, allocator}, + WriteForwardingPolicy{}, ConnectForwardingPolicy{}) {} +}; + +using ReadBufferingClient = BasicReadBufferingClient; +} // namespace StreamUtils \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Clients/ReadLoggingClient.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Clients/ReadLoggingClient.hpp new file mode 100644 index 00000000..627af07b --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Clients/ReadLoggingClient.hpp @@ -0,0 +1,23 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include "../Policies/ConnectForwardingPolicy.hpp" +#include "../Policies/ReadLoggingPolicy.hpp" +#include "../Policies/WriteForwardingPolicy.hpp" +#include "ClientProxy.hpp" + +namespace StreamUtils { + +struct ReadLoggingClient : ClientProxy { + ReadLoggingClient(Client &target, Print &log) + : ClientProxy(target, ReadLoggingPolicy{log}, + WriteForwardingPolicy{}, + ConnectForwardingPolicy{}) {} +}; + +} // namespace StreamUtils \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Clients/SpyingClient.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Clients/SpyingClient.hpp new file mode 100644 index 00000000..eaba9af5 --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Clients/SpyingClient.hpp @@ -0,0 +1,21 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include "../Policies/ConnectSpyingPolicy.hpp" +#include "../Policies/ReadSpyingPolicy.hpp" +#include "../Policies/WriteSpyingPolicy.hpp" +#include "ClientProxy.hpp" + +namespace StreamUtils { + +struct SpyingClient + : ClientProxy { + SpyingClient(Client &target, Print &log) + : ClientProxy( + target, {log}, {log}, {log}) {} +}; + +} // namespace StreamUtils diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Clients/WriteBufferingClient.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Clients/WriteBufferingClient.hpp new file mode 100644 index 00000000..4f207cb9 --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Clients/WriteBufferingClient.hpp @@ -0,0 +1,29 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include "../Policies/ConnectForwardingPolicy.hpp" +#include "../Policies/ReadForwardingPolicy.hpp" +#include "../Policies/WriteBufferingPolicy.hpp" +#include "../Ports/DefaultAllocator.hpp" +#include "ClientProxy.hpp" + +namespace StreamUtils { + +template +struct BasicWriteBufferingClient + : ClientProxy, + ConnectForwardingPolicy> { + explicit BasicWriteBufferingClient(Client &target, size_t capacity, + TAllocator allocator = TAllocator()) + : ClientProxy, + ConnectForwardingPolicy>( + target, ReadForwardingPolicy{}, + WriteBufferingPolicy{capacity, allocator}, + ConnectForwardingPolicy{}) {} +}; + +using WriteBufferingClient = BasicWriteBufferingClient; +} // namespace StreamUtils \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Clients/WriteLoggingClient.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Clients/WriteLoggingClient.hpp new file mode 100644 index 00000000..5b07a179 --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Clients/WriteLoggingClient.hpp @@ -0,0 +1,24 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include "../Policies/ConnectForwardingPolicy.hpp" +#include "../Policies/ReadForwardingPolicy.hpp" +#include "../Policies/WriteLoggingPolicy.hpp" +#include "ClientProxy.hpp" + +namespace StreamUtils { + +struct WriteLoggingClient + : ClientProxy { + WriteLoggingClient(Client &target, Print &log) + : ClientProxy(target, ReadForwardingPolicy{}, + WriteLoggingPolicy{log}, + ConnectForwardingPolicy{}) {} +}; + +} // namespace StreamUtils \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Clients/WriteWaitingClient.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Clients/WriteWaitingClient.hpp new file mode 100644 index 00000000..3e1894ca --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Clients/WriteWaitingClient.hpp @@ -0,0 +1,30 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include "../Policies/ConnectForwardingPolicy.hpp" +#include "../Policies/ReadForwardingPolicy.hpp" +#include "../Policies/WriteWaitingPolicy.hpp" +#include "ClientProxy.hpp" + +namespace StreamUtils { + +struct WriteWaitingClient + : ClientProxy { + WriteWaitingClient(Client &target, Polyfills::function wait = yield) + : ClientProxy( + target, ReadForwardingPolicy{}, + WriteWaitingPolicy{Polyfills::move(wait)}, + ConnectForwardingPolicy{}) {} + + void setTimeout(unsigned long timeout) { + Client::setTimeout(timeout); + _writer.setTimeout(timeout); + } +}; + +} // namespace StreamUtils \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Configuration.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Configuration.hpp new file mode 100644 index 00000000..ec149566 --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Configuration.hpp @@ -0,0 +1,53 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#ifndef STREAMUTILS_PRINT_FLUSH_EXISTS +#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_SAMD) || \ + defined(ARDUINO_ARCH_AVR) +#define STREAMUTILS_PRINT_FLUSH_EXISTS 1 +#else +#define STREAMUTILS_PRINT_FLUSH_EXISTS 0 +#endif +#endif + +#ifndef STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL +#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) || \ + defined(ARDUINO_ARCH_STM32) +#define STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL 1 +#else +#define STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL 0 +#endif +#endif + +#ifndef STREAMUTILS_ENABLE_EEPROM +#if defined(ARDUINO_ARCH_AVR) || defined(ARDUINO_ARCH_ESP8266) || \ + defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_STM32) || \ + defined(CORE_TEENSY) +#define STREAMUTILS_ENABLE_EEPROM 1 +#else +#define STREAMUTILS_ENABLE_EEPROM 0 +#endif +#endif + +#ifndef STREAMUTILS_USE_EEPROM_COMMIT +#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) +#define STREAMUTILS_USE_EEPROM_COMMIT 1 +#else +#define STREAMUTILS_USE_EEPROM_COMMIT 0 +#endif +#endif + +#ifndef STREAMUTILS_USE_EEPROM_UPDATE +#if defined(ARDUINO_ARCH_AVR) || defined(CORE_TEENSY) +#define STREAMUTILS_USE_EEPROM_UPDATE 1 +#else +#define STREAMUTILS_USE_EEPROM_UPDATE 0 +#endif +#endif + +#ifndef STREAMUTILS_STACK_BUFFER_MAX_SIZE +#define STREAMUTILS_STACK_BUFFER_MAX_SIZE 32 +#endif \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Helpers.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Helpers.hpp new file mode 100644 index 00000000..9bd219c7 --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Helpers.hpp @@ -0,0 +1,17 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +namespace StreamUtils { + +inline size_t readOrReadBytes(Stream &stream, char *buffer, size_t size) { + return stream.readBytes(buffer, size); +} + +inline size_t readOrReadBytes(Client &client, char *buffer, size_t size) { + return client.read(reinterpret_cast(buffer), size); +} + +} // namespace StreamUtils \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Policies/ConnectForwardingPolicy.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Policies/ConnectForwardingPolicy.hpp new file mode 100644 index 00000000..1580eee4 --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Policies/ConnectForwardingPolicy.hpp @@ -0,0 +1,35 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include + +#include "../Configuration.hpp" + +namespace StreamUtils { + +struct ConnectForwardingPolicy { + int connect(Client& target, const IPAddress& ip, uint16_t port) { + return target.connect(ip, port); + } + + int connect(Client& target, const char* ip, uint16_t port) { + return target.connect(ip, port); + } + + uint8_t connected(Client& target) { + return target.connected(); + } + + void stop(Client& target) { + target.stop(); + } + + bool operator_bool(Client& target) { + return target.operator bool(); + } +}; + +} // namespace StreamUtils \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Policies/ConnectSpyingPolicy.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Policies/ConnectSpyingPolicy.hpp new file mode 100644 index 00000000..c004ba02 --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Policies/ConnectSpyingPolicy.hpp @@ -0,0 +1,68 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include + +namespace StreamUtils { + +class ConnectSpyingPolicy { + public: + ConnectSpyingPolicy(Print& log) : _log(log) {} + + int connect(Client& target, const IPAddress& ip, uint16_t port) { + _log.print("connect('"); + _log.print(ip); + _log.print("', "); + _log.print(port); + _log.print(") -> "); + + int result = target.connect(ip, port); + _log.println(result); + + return result; + } + + int connect(Client& target, const char* ip, uint16_t port) { + _log.print("connect('"); + _log.print(ip); + _log.print("', "); + _log.print(port); + _log.print(") -> "); + + int result = target.connect(ip, port); + _log.println(result); + + return result; + } + + uint8_t connected(Client& target) { + _log.print("connected() -> "); + + uint8_t result = target.connected(); + _log.println(result); + + return result; + } + + void stop(Client& target) { + _log.print("stop()"); + target.stop(); + } + + bool operator_bool(Client& target) { + _log.print("operator bool() -> "); + + bool result = target.operator bool(); + _log.println(result ? "true" : "false"); + + return result; + } + + private: + Print& _log; +}; + +} // namespace StreamUtils \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Policies/HammingDecodingPolicy.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Policies/HammingDecodingPolicy.hpp new file mode 100644 index 00000000..37bb2ffe --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Policies/HammingDecodingPolicy.hpp @@ -0,0 +1,137 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#if defined(WIN32) || defined(__WIN32) || defined(__WIN32__) +#include +#else +#include +#endif + +#include +#include + +#include "../Configuration.hpp" +#include "../Helpers.hpp" + +namespace StreamUtils { + +template +class HammingDecodingPolicy; + +template +class HammingDecodingPolicy<7, 4, TAllocator> { + const static size_t sizeAllowedOnStack = STREAMUTILS_STACK_BUFFER_MAX_SIZE; + + public: + HammingDecodingPolicy(TAllocator allocator = TAllocator()) + : _allocator(allocator) {} + + int available(Stream &stream) { + int n = stream.available(); + if (_remainder >= 0) + n++; + return n / 2; + } + + template // Stream or Client + int read(TTarget &target) { + if (_remainder < 0) { + _remainder = target.read(); + if (_remainder < 0) + return -1; + } + int c = target.read(); + if (c < 0) + return -1; + int result = decode(_remainder, c); + _remainder = -1; + return result; + } + + int peek(Stream &stream) { + if (_remainder < 0) { + _remainder = stream.read(); + if (_remainder < 0) + return -1; + } + int c = stream.peek(); + if (c < 0) + return -1; + return decode(_remainder, c); + } + + size_t readBytes(Stream &stream, char *buffer, size_t size) { + return doReadBytes(stream, buffer, size); + } + + int read(Client &client, uint8_t *buffer, size_t size) { + return doReadBytes(client, reinterpret_cast(buffer), size); + } + + private: + // Decode 7-bits to 4-bits using Hamming(7,4) + uint8_t decode(uint8_t input) { + // table is packed: each element contains two 4-bits values + static uint8_t table[] = { + 0x00, 0x03, 0x05, 0xE7, 0x09, 0xEB, 0xED, 0xEE, 0x03, 0x33, 0x4D, + 0x63, 0x8D, 0xA3, 0xDD, 0xED, 0x05, 0x2B, 0x55, 0x65, 0x8B, 0xBB, + 0xC5, 0xEB, 0x81, 0x63, 0x65, 0x66, 0x88, 0x8B, 0x8D, 0x6F, 0x09, + 0x27, 0x47, 0x77, 0x99, 0xA9, 0xC9, 0xE7, 0x41, 0xA3, 0x44, 0x47, + 0xA9, 0xAA, 0x4D, 0xAF, 0x21, 0x22, 0xC5, 0x27, 0xC9, 0x2B, 0xCC, + 0xCF, 0x11, 0x21, 0x41, 0x6F, 0x81, 0xAF, 0xCF, 0xFF}; + uint8_t elem = table[input / 2]; + if (input % 2) + return elem & 0x0f; + + else + return elem >> 4; + } + + uint8_t decode(uint8_t first, uint8_t second) { + return decode(first) << 4 | decode(second); + } + + template // Stream or Client + size_t doReadBytes(TTarget &target, char *output, size_t outputSize) { + char *buffer; + size_t bufferSize = outputSize * 2; + + if (bufferSize > sizeAllowedOnStack) { + buffer = static_cast(_allocator.allocate(bufferSize)); + if (!buffer) { + // allocation failed => use the input buffer + bufferSize = outputSize; + buffer = output; + } + } else { + buffer = static_cast(alloca(bufferSize)); + } + + size_t loadedSize = 0; + if (_remainder >= 0) + buffer[loadedSize++] = _remainder; + + loadedSize += + readOrReadBytes(target, buffer + loadedSize, bufferSize - loadedSize); + for (size_t i = 0; i < loadedSize / 2; i++) + output[i] = decode(buffer[2 * i], buffer[2 * i + 1]); + + if (loadedSize % 2) + _remainder = buffer[loadedSize - 1]; + else + _remainder = -1; + + if (bufferSize > sizeAllowedOnStack) + _allocator.deallocate(buffer); + + return loadedSize / 2; + } + + TAllocator _allocator; + char _remainder = -1; +}; + +} // namespace StreamUtils diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Policies/HammingEncodingPolicy.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Policies/HammingEncodingPolicy.hpp new file mode 100644 index 00000000..f7d281ec --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Policies/HammingEncodingPolicy.hpp @@ -0,0 +1,116 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#if defined(WIN32) || defined(__WIN32) || defined(__WIN32__) +#include +#else +#include +#endif + +#include "../Configuration.hpp" + +namespace StreamUtils { + +template +class HammingEncodingPolicy; + +template +class HammingEncodingPolicy<7, 4, TAllocator> { + const static size_t sizeAllowedOnStack = STREAMUTILS_STACK_BUFFER_MAX_SIZE; + + public: + HammingEncodingPolicy(TAllocator allocator = TAllocator()) + : _allocator(allocator) {} + + size_t write(Print &target, const uint8_t *data, size_t size) { + if (!flushRemainder(target)) + return 0; + + size_t bufferSize = size * 2; + uint8_t *buffer; + if (bufferSize > sizeAllowedOnStack) { + buffer = static_cast(_allocator.allocate(bufferSize)); + if (!buffer) { + bufferSize = sizeAllowedOnStack; + buffer = static_cast(alloca(bufferSize)); + } + } else { + buffer = static_cast(alloca(bufferSize)); + } + + for (size_t i = 0, j = 0; j < bufferSize; i++) { + buffer[j++] = encode(data[i] >> 4); + buffer[j++] = encode(data[i] & 0x0f); + } + size_t n = target.write(buffer, bufferSize); + if (n & 1) { + _remainder = buffer[n]; + ++n; + } + + if (bufferSize > sizeAllowedOnStack) + _allocator.deallocate(buffer); + + return n / 2; + } + + size_t write(Print &target, uint8_t data) { + uint8_t first = encode(data >> 4); + uint8_t second = encode(data & 0x0f); + + if (!flushRemainder(target)) + return 0; + + if (!target.write(first)) + return 0; + + if (!target.write(second)) + _remainder = second; + + return 1; + } + + void flush(Stream &target) { + flushRemainder(target); + target.flush(); + } + + void flush(Print &target) { + flushRemainder(target); +#if STREAMUTILS_PRINT_FLUSH_EXISTS + target.flush(); +#endif + } + + void implicitFlush(Print &target) { + flushRemainder(target); + } + + private: + // Encode 4-bits to 7-bits using Hamming(7,4) + uint8_t encode(uint8_t input) { + static uint8_t table[] = {0x00, 0x71, 0x62, 0x13, 0x54, 0x25, 0x36, 0x47, + 0x38, 0x49, 0x5A, 0x2B, 0x6C, 0x1D, 0x0E, 0x7F}; + return table[input]; + } + + template + bool flushRemainder(TTarget &target) { + if (_remainder < 0) + return true; + + if (!target.write(_remainder)) + return false; + + _remainder = -1; + return true; + } + + TAllocator _allocator; + int16_t _remainder = -1; +}; + +} // namespace StreamUtils \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Policies/ReadBufferingPolicy.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Policies/ReadBufferingPolicy.hpp new file mode 100644 index 00000000..10bc3305 --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Policies/ReadBufferingPolicy.hpp @@ -0,0 +1,97 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include + +#include "../Buffers/LinearBuffer.hpp" +#include "../Helpers.hpp" + +namespace StreamUtils { + +template +struct ReadBufferingPolicy { + ReadBufferingPolicy(size_t capacity, TAllocator allocator = TAllocator()) + : _buffer(capacity, allocator) {} + + ReadBufferingPolicy(const ReadBufferingPolicy &other) + : _buffer(other._buffer) {} + + int available(Stream &stream) { + return stream.available() + _buffer.available(); + } + + template // Stream or Client + int read(TTarget &target) { + if (!_buffer) + return target.read(); + + if (_buffer.available() > 0) + return _buffer.read(); + + size_t avail = static_cast(target.available()); + if (avail <= 1) + return target.read(); + + _buffer.reloadFrom(target, avail); + return _buffer.read(); + } + + int peek(Stream &stream) { + return isEmpty() ? stream.peek() : _buffer.peek(); + } + + size_t readBytes(Stream &stream, char *buffer, size_t size) { + return doReadBytes(stream, buffer, size); + } + + int read(Client &client, uint8_t *buffer, size_t size) { + return doReadBytes(client, reinterpret_cast(buffer), size); + } + + private: + bool isEmpty() const { + return _buffer.available() == 0; + } + + LinearBuffer _buffer; + + template // Stream or Client + size_t doReadBytes(TTarget &target, char *buffer, size_t size) { + if (!_buffer) + return readOrReadBytes(target, buffer, size); + + size_t result = 0; + + // can we read from buffer? + if (_buffer.available() > 0) { + size_t bytesRead = _buffer.readBytes(buffer, size); + result += bytesRead; + buffer += bytesRead; + size -= bytesRead; + } + + // still something to read? + if (size > 0) { + // (at this point, the buffer is empty) + + size_t avail = static_cast(target.available()); + + // should we use the buffer? + if (avail > size && size < _buffer.capacity()) { + _buffer.reloadFrom(target, avail); + size_t bytesRead = _buffer.readBytes(buffer, size); + result += bytesRead; + } else { + // we can bypass the buffer + result += readOrReadBytes(target, buffer, size); + } + } + + return result; + } +}; // namespace StreamUtils + +} // namespace StreamUtils \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Policies/ReadForwardingPolicy.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Policies/ReadForwardingPolicy.hpp new file mode 100644 index 00000000..02138aba --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Policies/ReadForwardingPolicy.hpp @@ -0,0 +1,33 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include + +namespace StreamUtils { + +struct ReadForwardingPolicy { + int available(Stream &target) { + return target.available(); + } + + int read(Stream &target) { + return target.read(); + } + + int peek(Stream &target) { + return target.peek(); + } + + size_t readBytes(Stream &target, char *buffer, size_t size) { + return target.readBytes(buffer, size); + } + + int read(Client &target, uint8_t *buffer, size_t size) { + return target.read(buffer, size); + } +}; + +} // namespace StreamUtils \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Policies/ReadLoggingPolicy.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Policies/ReadLoggingPolicy.hpp new file mode 100644 index 00000000..ce614c34 --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Policies/ReadLoggingPolicy.hpp @@ -0,0 +1,46 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include + +namespace StreamUtils { + +class ReadLoggingPolicy { + public: + ReadLoggingPolicy(Print &log) : _log(log) {} + + int available(Stream &stream) { + return stream.available(); + } + + int read(Stream &stream) { + int result = stream.read(); + if (result >= 0) + _log.write(result); + return result; + } + + int peek(Stream &stream) { + return stream.peek(); + } + + size_t readBytes(Stream &stream, char *buffer, size_t size) { + size_t result = stream.readBytes(buffer, size); + _log.write(buffer, result); + return result; + } + + int read(Client &client, uint8_t *buffer, size_t size) { + int result = client.read(buffer, size); + _log.write(buffer, result); + return result; + } + + private: + Print &_log; +}; + +} // namespace StreamUtils \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Policies/ReadSpyingPolicy.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Policies/ReadSpyingPolicy.hpp new file mode 100644 index 00000000..0cfb6ac2 --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Policies/ReadSpyingPolicy.hpp @@ -0,0 +1,64 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include + +namespace StreamUtils { + +class ReadSpyingPolicy { + public: + ReadSpyingPolicy(Print &log) : _log(log) {} + + int available(Stream &target) { + int result = target.available(); + _log.print("available() -> "); + _log.println(result); + return result; + } + + int read(Stream &target) { + int result = target.read(); + _log.print("read() -> "); + _log.println(result); + return result; + } + + int peek(Stream &target) { + int result = target.peek(); + _log.print("peek() -> "); + _log.println(result); + return result; + } + + size_t readBytes(Stream &target, char *buffer, size_t size) { + size_t result = target.readBytes(buffer, size); + _log.print("readBytes("); + _log.print(size); + _log.print(") -> "); + _log.print(result); + if (size > result) + _log.print(" [timeout]"); + _log.println(); + return result; + } + + int read(Client &target, uint8_t *buffer, size_t size) { + int result = target.read(buffer, size); + _log.print("read("); + _log.print(size); + _log.print(") -> "); + _log.print(result); + if (static_cast(size) > result) + _log.print(" [timeout]"); + _log.println(); + return result; + } + + private: + Print &_log; +}; + +} // namespace StreamUtils diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Policies/ReadThrottlingPolicy.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Policies/ReadThrottlingPolicy.hpp new file mode 100644 index 00000000..51e01a81 --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Policies/ReadThrottlingPolicy.hpp @@ -0,0 +1,47 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include + +namespace StreamUtils { + +template +struct ReadThrottlingPolicy { + ReadThrottlingPolicy(TThrottler throttler) : _throttler(throttler) {} + + int available(Stream &stream) { + return stream.available(); + } + + int read(Stream &stream) { + _throttler.throttle(); + return stream.read(); + } + + int peek(Stream &stream) { + _throttler.throttle(); + return stream.peek(); + } + + size_t readBytes(Stream &stream, char *buffer, size_t size) { + for (size_t i = 0; i < size; i++) { + int c = read(stream); + if (c < 0) + return i; + buffer[i] = c; + } + return size; + } + + const TThrottler &throttler() const { + return _throttler; + } + + private: + TThrottler _throttler; +}; + +} // namespace StreamUtils \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Policies/WriteBufferingPolicy.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Policies/WriteBufferingPolicy.hpp new file mode 100644 index 00000000..53a2b974 --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Policies/WriteBufferingPolicy.hpp @@ -0,0 +1,78 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include + +#include "../Buffers/LinearBuffer.hpp" +#include "../Configuration.hpp" + +namespace StreamUtils { + +template +struct WriteBufferingPolicy { + public: + WriteBufferingPolicy(size_t capacity, TAllocator allocator) + : _buffer(capacity, allocator) {} + + size_t write(Print &target, const uint8_t *data, size_t size) { + size_t result = 0; + + // continue to fill the buffer? + if (!_buffer.isEmpty()) { + size_t n = _buffer.write(data, size); + data += n; + size -= n; + result += n; + + // time to flush? + if (_buffer.isFull()) { + _buffer.flushInto(target); + } + } + + // something left to write? + if (size > 0) { + // can we bypass the buffer? + if (size >= _buffer.capacity()) { + result += target.write(data, size); + } else { + result += _buffer.write(data, size); + } + } + return result; + } + + size_t write(Print &target, uint8_t data) { + if (!_buffer) + return target.write(data); + + _buffer.write(data); + if (_buffer.isFull()) + _buffer.flushInto(target); + return 1; + } + + void flush(Stream &target) { + _buffer.flushInto(target); + target.flush(); + } + + void flush(Print &target) { + _buffer.flushInto(target); +#if STREAMUTILS_PRINT_FLUSH_EXISTS + target.flush(); +#endif + } + + void implicitFlush(Print &target) { + _buffer.flushInto(target); + } + + private: + LinearBuffer _buffer; +}; + +} // namespace StreamUtils \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Policies/WriteForwardingPolicy.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Policies/WriteForwardingPolicy.hpp new file mode 100644 index 00000000..fac36cce --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Policies/WriteForwardingPolicy.hpp @@ -0,0 +1,27 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include +#include + +#include "../Configuration.hpp" + +namespace StreamUtils { + +struct WriteForwardingPolicy { + template + size_t write(Stream &stream, Args... args) { + return stream.write(args...); + } + + void flush(Stream &stream) { + stream.flush(); + } + + void implicitFlush(Stream &) {} +}; + +} // namespace StreamUtils \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Policies/WriteLoggingPolicy.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Policies/WriteLoggingPolicy.hpp new file mode 100644 index 00000000..c4378af3 --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Policies/WriteLoggingPolicy.hpp @@ -0,0 +1,39 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include +#include "../Configuration.hpp" + +namespace StreamUtils { + +class WriteLoggingPolicy { + public: + WriteLoggingPolicy(Print &log) : _log(log) {} + + size_t write(Print &target, const uint8_t *buffer, size_t size) { + size_t result = target.write(buffer, size); + _log.write(buffer, result); + return result; + } + + size_t write(Print &target, uint8_t c) { + size_t result = target.write(c); + _log.write(c); + return result; + } + + template + void flush(TTarget &target) { + target.flush(); + } + + void implicitFlush(Print &) {} + + private: + Print &_log; +}; + +} // namespace StreamUtils \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Policies/WriteSpyingPolicy.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Policies/WriteSpyingPolicy.hpp new file mode 100644 index 00000000..24ee2f3e --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Policies/WriteSpyingPolicy.hpp @@ -0,0 +1,55 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include + +#include "../Configuration.hpp" + +namespace StreamUtils { + +class WriteSpyingPolicy { + public: + WriteSpyingPolicy(Print &log) : _log(log) {} + + size_t write(Print &stream, const uint8_t *buffer, size_t size) { + _log.print("write('"); + for (size_t i = 0; i < size; i++) { + _log.write(buffer[i]); + } + _log.print("', "); + _log.print(size); + _log.print(") -> "); + + size_t result = stream.write(buffer, size); + _log.println(result); + + return result; + } + + size_t write(Print &stream, uint8_t data) { + _log.print("write('"); + _log.write(data); + _log.print("') -> "); + + size_t result = stream.write(data); + _log.println(result); + + return result; + } + + template + void flush(TTarget &target) { + _log.println("flush()"); + target.flush(); + } + + void implicitFlush(Print &) {} + + private: + Print &_log; +}; + +} // namespace StreamUtils diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Policies/WriteWaitingPolicy.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Policies/WriteWaitingPolicy.hpp new file mode 100644 index 00000000..744fb80c --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Policies/WriteWaitingPolicy.hpp @@ -0,0 +1,63 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include + +#include "../Buffers/LinearBuffer.hpp" +#include "../Configuration.hpp" +#include "../Polyfills.hpp" + +namespace StreamUtils { + +struct WriteWaitingPolicy { + public: + WriteWaitingPolicy(Polyfills::function wait) + : _wait(Polyfills::move(wait)), _timeout(1000) {} + + size_t write(Print &target, const uint8_t *data, size_t size) { + unsigned long startTime = millis(); + size_t totalWritten = 0; + + for (;;) { + size_t n = target.write(data, size); + size -= n; + data += n; + totalWritten += n; + if (size == 0 || millis() - startTime >= _timeout) + return totalWritten; + _wait(); + } + } + + size_t write(Print &target, uint8_t data) { + unsigned long startTime = millis(); + + for (;;) { + if (target.write(data)) + return 1; + if (millis() - startTime >= _timeout) + return 0; + _wait(); + } + } + + template + void flush(TTarget &target) { + target.flush(); + } + + void implicitFlush(Print &) {} + + void setTimeout(unsigned long timeout) { + _timeout = timeout; + } + + private: + Polyfills::function _wait; + unsigned long _timeout; +}; + +} // namespace StreamUtils \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Polyfills.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Polyfills.hpp new file mode 100644 index 00000000..aa4b50bc --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Polyfills.hpp @@ -0,0 +1,60 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +namespace StreamUtils { +namespace Polyfills { + +template +struct remove_reference { + using type = T; +}; + +template +struct remove_reference { + using type = T; +}; + +template +typename remove_reference::type &&move(T &&t) { + return static_cast::type &&>(t); +} + +// poor man's std::function +class function { + struct callable_base { + virtual void operator()() = 0; + virtual ~callable_base() {} + }; + + template + struct callable : callable_base { + Functor functor; + callable(Functor functor) : functor(functor) {} + virtual void operator()() { + functor(); + } + }; + + callable_base *_callable; + + public: + template + function(Functor f) { + _callable = new callable(f); + } + function(function &&src) { + _callable = src._callable, src._callable = 0; + } + ~function() { + delete _callable; + } + void operator()() const { + (*_callable)(); + } +}; + +} // namespace Polyfills +} // namespace StreamUtils \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Ports/ArduinoThrottler.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Ports/ArduinoThrottler.hpp new file mode 100644 index 00000000..0efda001 --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Ports/ArduinoThrottler.hpp @@ -0,0 +1,29 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +namespace StreamUtils { + +class ArduinoThrottler { + public: + ArduinoThrottler(uint32_t rate) : _interval(1000000 / rate), _last(0) {} + + void throttle() { + auto now = micros(); + auto elapsed = now - _last; + + if (elapsed < _interval) { + delayMicroseconds(_interval - elapsed); + } + + _last = now; + } + + private: + unsigned long _interval; + unsigned long _last; +}; + +} // namespace StreamUtils \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Ports/DefaultAllocator.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Ports/DefaultAllocator.hpp new file mode 100644 index 00000000..e3bf1541 --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Ports/DefaultAllocator.hpp @@ -0,0 +1,21 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +namespace StreamUtils { + +#include // malloc, free, size_t + +struct DefaultAllocator { + void* allocate(size_t n) { + return malloc(n); + } + + void deallocate(void* p) { + free(p); + } +}; + +} // namespace StreamUtils \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Prints/BufferingPrint.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Prints/BufferingPrint.hpp new file mode 100644 index 00000000..904ba219 --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Prints/BufferingPrint.hpp @@ -0,0 +1,22 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include "../Policies/WriteBufferingPolicy.hpp" +#include "../Ports/DefaultAllocator.hpp" +#include "PrintProxy.hpp" + +namespace StreamUtils { + +template +struct BasicBufferingPrint : PrintProxy> { + explicit BasicBufferingPrint(Print &upstream, size_t capacity, + TAllocator allocator = TAllocator()) + : PrintProxy>(upstream, + {capacity, allocator}) {} +}; + +using BufferingPrint = BasicBufferingPrint; +} // namespace StreamUtils \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Prints/HammingPrint.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Prints/HammingPrint.hpp new file mode 100644 index 00000000..9d0236ab --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Prints/HammingPrint.hpp @@ -0,0 +1,19 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include "../Policies/HammingEncodingPolicy.hpp" +#include "../Ports/DefaultAllocator.hpp" +#include "PrintProxy.hpp" + +namespace StreamUtils { + +template +using BasicHammingPrint = PrintProxy>; + +template +using HammingPrint = BasicHammingPrint; + +} // namespace StreamUtils \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Prints/LoggingPrint.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Prints/LoggingPrint.hpp new file mode 100644 index 00000000..cfd33741 --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Prints/LoggingPrint.hpp @@ -0,0 +1,17 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include "../Policies/WriteLoggingPolicy.hpp" +#include "PrintProxy.hpp" + +namespace StreamUtils { + +struct LoggingPrint : PrintProxy { + LoggingPrint(Print &upstream, Print &log) + : PrintProxy(upstream, {log}) {} +}; + +} // namespace StreamUtils \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Prints/PrintProxy.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Prints/PrintProxy.hpp new file mode 100644 index 00000000..fcb6860e --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Prints/PrintProxy.hpp @@ -0,0 +1,50 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include + +#include "../Configuration.hpp" +#include "../Polyfills.hpp" + +namespace StreamUtils { + +template +class PrintProxy : public Print { + public: + explicit PrintProxy(Print &upstream, WritePolicy writer = WritePolicy{}) + : _target(upstream), _writer(Polyfills::move(writer)) {} + + PrintProxy(const PrintProxy &other) + : _target(other._target), _writer(other._writer) {} + + ~PrintProxy() { + _writer.implicitFlush(_target); + } + + size_t write(const uint8_t *buffer, size_t size) override { + return _writer.write(_target, buffer, size); + } + + size_t write(uint8_t data) override { + return _writer.write(_target, data); + } + +#if STREAMUTILS_PRINT_FLUSH_EXISTS + void flush() override { +#else + void flush() { +#endif + _writer.flush(_target); + } + + using Print::write; + + protected: + Print &_target; + WritePolicy _writer; +}; + +} // namespace StreamUtils \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Prints/SpyingPrint.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Prints/SpyingPrint.hpp new file mode 100644 index 00000000..c8262ef1 --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Prints/SpyingPrint.hpp @@ -0,0 +1,17 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include "../Policies/WriteSpyingPolicy.hpp" +#include "PrintProxy.hpp" + +namespace StreamUtils { + +struct SpyingPrint : PrintProxy { + SpyingPrint(Print &target, Print &log) + : PrintProxy(target, WriteSpyingPolicy{log}) {} +}; + +} // namespace StreamUtils diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Prints/StringPrint.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Prints/StringPrint.hpp new file mode 100644 index 00000000..0f344980 --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Prints/StringPrint.hpp @@ -0,0 +1,52 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include +#include + +#include "../Polyfills.hpp" + +namespace StreamUtils { + +class StringPrint : public Print { + public: + StringPrint() {} + + explicit StringPrint(String str) : _str(Polyfills::move(str)) {} + + size_t write(const uint8_t* p, size_t n) override { + for (size_t i = 0; i < n; i++) { + uint8_t c = p[i]; + if (c == 0) + return i; + write(c); + } + return n; + } + + size_t write(uint8_t c) override { + if (c == 0) + return 0; + _str += static_cast(c); + return 1; + } + + const String& str() const { + return _str; + } + + void str(String str) { + _str = Polyfills::move(str); + } + + void clear() { + _str = ""; + } + + private: + String _str; +}; +} // namespace StreamUtils \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Prints/WaitingPrint.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Prints/WaitingPrint.hpp new file mode 100644 index 00000000..05d05b47 --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Prints/WaitingPrint.hpp @@ -0,0 +1,22 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include "../Policies/WriteWaitingPolicy.hpp" +#include "PrintProxy.hpp" + +namespace StreamUtils { + +struct WaitingPrint : PrintProxy { + WaitingPrint(Print &target, Polyfills::function wait = yield) + : PrintProxy( + target, WriteWaitingPolicy{Polyfills::move(wait)}) {} + + void setTimeout(unsigned long timeout) { + _writer.setTimeout(timeout); + } +}; + +} // namespace StreamUtils \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Streams/EepromStream.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Streams/EepromStream.hpp new file mode 100644 index 00000000..d28bccbd --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Streams/EepromStream.hpp @@ -0,0 +1,76 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include "../Configuration.hpp" + +#if STREAMUTILS_ENABLE_EEPROM + +#include +#include + +namespace StreamUtils { + +class EepromStream : public Stream { + public: + EepromStream(size_t address, size_t size) + : _readAddress(address), _writeAddress(address), _end(address + size) {} + + int available() override { + return _end - _readAddress; + } + + int read() override { + if (_readAddress >= _end) + return -1; + return EEPROM.read(_readAddress++); + } + + int peek() override { + if (_readAddress >= _end) + return -1; + return EEPROM.read(_readAddress); + } + + void flush() override { +#if STREAMUTILS_USE_EEPROM_COMMIT + EEPROM.commit(); +#endif + } + + using Print::write; + + size_t write(const uint8_t *buffer, size_t size) override { + size_t remaining = _end - _writeAddress; + if (size > remaining) + size = remaining; + for (size_t i = 0; i < size; i++) { +#if STREAMUTILS_USE_EEPROM_UPDATE + EEPROM.update(_writeAddress++, buffer[i]); +#else + EEPROM.write(_writeAddress++, buffer[i]); +#endif + } + return size; + } + + size_t write(uint8_t data) override { + if (_writeAddress >= _end) + return 0; +#if STREAMUTILS_USE_EEPROM_UPDATE + EEPROM.update(_writeAddress++, data); +#else + EEPROM.write(_writeAddress++, data); +#endif + return 1; + } + + private: + size_t _readAddress, _writeAddress, _end; +}; + +} // namespace StreamUtils + +#endif diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Streams/HammingDecodingStream.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Streams/HammingDecodingStream.hpp new file mode 100644 index 00000000..4003eee0 --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Streams/HammingDecodingStream.hpp @@ -0,0 +1,22 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include "../Policies/HammingDecodingPolicy.hpp" +#include "../Policies/WriteForwardingPolicy.hpp" +#include "../Ports/DefaultAllocator.hpp" +#include "StreamProxy.hpp" + +namespace StreamUtils { + +template +using BasicHammingDecodingStream = + StreamProxy, WriteForwardingPolicy>; + +template +using HammingDecodingStream = + BasicHammingDecodingStream; + +} // namespace StreamUtils diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Streams/HammingEncodingStream.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Streams/HammingEncodingStream.hpp new file mode 100644 index 00000000..59a80934 --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Streams/HammingEncodingStream.hpp @@ -0,0 +1,22 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include "../Policies/HammingEncodingPolicy.hpp" +#include "../Policies/ReadForwardingPolicy.hpp" +#include "../Ports/DefaultAllocator.hpp" +#include "StreamProxy.hpp" + +namespace StreamUtils { + +template +using BasicHammingEncodingStream = + StreamProxy>; + +template +using HammingEncodingStream = + BasicHammingEncodingStream; + +} // namespace StreamUtils diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Streams/HammingStream.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Streams/HammingStream.hpp new file mode 100644 index 00000000..68e8f70d --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Streams/HammingStream.hpp @@ -0,0 +1,21 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include "../Policies/HammingDecodingPolicy.hpp" +#include "../Policies/HammingEncodingPolicy.hpp" +#include "../Ports/DefaultAllocator.hpp" +#include "StreamProxy.hpp" + +namespace StreamUtils { + +template +using BasicHammingStream = StreamProxy, + HammingEncodingPolicy>; + +template +using HammingStream = BasicHammingStream; + +} // namespace StreamUtils diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Streams/LoggingStream.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Streams/LoggingStream.hpp new file mode 100644 index 00000000..3e7e789a --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Streams/LoggingStream.hpp @@ -0,0 +1,19 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include "../Policies/ReadLoggingPolicy.hpp" +#include "../Policies/WriteLoggingPolicy.hpp" +#include "StreamProxy.hpp" + +namespace StreamUtils { + +struct LoggingStream : StreamProxy { + LoggingStream(Stream& target, Print& log) + : StreamProxy( + target, ReadLoggingPolicy{log}, WriteLoggingPolicy{log}) {} +}; + +} // namespace StreamUtils \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Streams/MemoryStream.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Streams/MemoryStream.hpp new file mode 100644 index 00000000..1b027e4f --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Streams/MemoryStream.hpp @@ -0,0 +1,61 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include + +#include "../Buffers/CircularBuffer.hpp" +#include "../Configuration.hpp" +#include "../Ports/DefaultAllocator.hpp" + +namespace StreamUtils { + +template +class BasicMemoryStream : public Stream { + public: + BasicMemoryStream(size_t capacity, TAllocator allocator = TAllocator()) + : _buffer(capacity, allocator) {} + + BasicMemoryStream(const BasicMemoryStream &src) : _buffer(src._buffer) {} + + int available() override { + return static_cast(_buffer.available()); + } + + int peek() override { + return _buffer.isEmpty() ? -1 : _buffer.peek(); + } + + int read() override { + return _buffer.isEmpty() ? -1 : _buffer.read(); + } + +#if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL + size_t readBytes(char *data, size_t size) override { + return _buffer.readBytes(data, size); + } +#endif + + size_t write(uint8_t data) override { + return _buffer.isFull() ? 0 : _buffer.write(data); + } + + size_t write(const uint8_t *data, size_t size) override { + return _buffer.write(data, size); + } + + using Stream::write; + + void flush() override { + _buffer.clear(); + } + + private: + CircularBuffer _buffer; +}; + +using MemoryStream = BasicMemoryStream; + +} // namespace StreamUtils \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Streams/ReadBufferingStream.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Streams/ReadBufferingStream.hpp new file mode 100644 index 00000000..11805937 --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Streams/ReadBufferingStream.hpp @@ -0,0 +1,28 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include "../Policies/ReadBufferingPolicy.hpp" +#include "../Policies/WriteForwardingPolicy.hpp" +#include "../Ports/DefaultAllocator.hpp" +#include "StreamProxy.hpp" + +namespace StreamUtils { + +template +class BasicReadBufferingStream + : public StreamProxy, + WriteForwardingPolicy> { + using base_type = + StreamProxy, WriteForwardingPolicy>; + + public: + explicit BasicReadBufferingStream(Stream &upstream, size_t capacity, + TAllocator allocator = TAllocator()) + : base_type(upstream, {capacity, allocator}, {}) {} +}; + +using ReadBufferingStream = BasicReadBufferingStream; +} // namespace StreamUtils \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Streams/ReadLoggingStream.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Streams/ReadLoggingStream.hpp new file mode 100644 index 00000000..50f92b01 --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Streams/ReadLoggingStream.hpp @@ -0,0 +1,19 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include "../Policies/ReadLoggingPolicy.hpp" +#include "../Policies/WriteForwardingPolicy.hpp" +#include "StreamProxy.hpp" + +namespace StreamUtils { + +struct ReadLoggingStream + : StreamProxy { + ReadLoggingStream(Stream &target, Print &log) + : StreamProxy( + target, ReadLoggingPolicy{log}, WriteForwardingPolicy{}) {} +}; +} // namespace StreamUtils \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Streams/ReadThrottlingStream.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Streams/ReadThrottlingStream.hpp new file mode 100644 index 00000000..edafd085 --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Streams/ReadThrottlingStream.hpp @@ -0,0 +1,35 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include "../Policies/ReadThrottlingPolicy.hpp" +#include "../Policies/WriteForwardingPolicy.hpp" +#include "StreamProxy.hpp" + +#ifdef ARDUINO +#include "../Ports/ArduinoThrottler.hpp" +#endif + +namespace StreamUtils { + +template +class BasicReadThrottlingStream + : public StreamProxy, + WriteForwardingPolicy> { + public: + BasicReadThrottlingStream(Stream& upstream, + TThrottler throttler = TThrottler()) + : StreamProxy, WriteForwardingPolicy>( + upstream, ReadThrottlingPolicy(throttler), {}) {} + + const TThrottler& throttler() const { + return this->_reader.throttler(); + } +}; + +#ifdef ARDUINO +using ReadThrottlingStream = BasicReadThrottlingStream; +#endif +} // namespace StreamUtils \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Streams/SpyingStream.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Streams/SpyingStream.hpp new file mode 100644 index 00000000..c323a543 --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Streams/SpyingStream.hpp @@ -0,0 +1,19 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include "../Policies/ReadSpyingPolicy.hpp" +#include "../Policies/WriteSpyingPolicy.hpp" +#include "StreamProxy.hpp" + +namespace StreamUtils { + +struct SpyingStream : StreamProxy { + SpyingStream(Stream &target, Print &log) + : StreamProxy( + target, ReadSpyingPolicy{log}, WriteSpyingPolicy{log}) {} +}; + +} // namespace StreamUtils diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Streams/StreamProxy.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Streams/StreamProxy.hpp new file mode 100644 index 00000000..c2c95226 --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Streams/StreamProxy.hpp @@ -0,0 +1,68 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include +#include "../Polyfills.hpp" + +namespace StreamUtils { + +template +class StreamProxy : public Stream { + public: + explicit StreamProxy(Stream &upstream, ReadPolicy reader = ReadPolicy{}, + WritePolicy writer = WritePolicy{}) + : _upstream(upstream), + _reader(reader), + _writer(Polyfills::move(writer)) {} + + StreamProxy(const StreamProxy &other) + : _upstream(other._upstream), + _reader(other._reader), + _writer(other._writer) {} + + ~StreamProxy() { + _writer.implicitFlush(_upstream); + } + + size_t write(const uint8_t *buffer, size_t size) override { + return _writer.write(_upstream, buffer, size); + } + + size_t write(uint8_t data) override { + return _writer.write(_upstream, data); + } + + using Stream::write; + + int available() override { + return _reader.available(_upstream); + } + + int read() override { + return _reader.read(_upstream); + } + + int peek() override { + return _reader.peek(_upstream); + } + + void flush() override { + _writer.flush(_upstream); + } + +#if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL + size_t readBytes(char *buffer, size_t size) override { + return _reader.readBytes(_upstream, buffer, size); + } +#endif + + protected: + Stream &_upstream; + ReadPolicy _reader; + WritePolicy _writer; +}; + +} // namespace StreamUtils \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Streams/StringStream.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Streams/StringStream.hpp new file mode 100644 index 00000000..fa89ecf6 --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Streams/StringStream.hpp @@ -0,0 +1,77 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include +#include + +#include "../Configuration.hpp" +#include "../Polyfills.hpp" + +namespace StreamUtils { + +class StringStream : public Stream { + public: + StringStream() {} + + explicit StringStream(String str) : _str(Polyfills::move(str)) {} + + size_t write(const uint8_t* p, size_t n) override { + for (size_t i = 0; i < n; i++) { + uint8_t c = p[i]; + if (c == 0) + return i; + write(c); + } + return n; + } + + size_t write(uint8_t c) override { + if (c == 0) + return 0; + _str += static_cast(c); + return 1; + } + + const String& str() const { + return _str; + } + + void str(String str) { + _str = Polyfills::move(str); + } + + int available() override { + return _str.length(); + } + + int read() override { + if (_str.length() == 0) + return -1; + char c = _str[0]; + _str.remove(0, 1); + return c; + } + +#if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL + size_t readBytes(char* buffer, size_t length) override { + if (length > _str.length()) + length = _str.length(); + _str.toCharArray(buffer, length); + _str.remove(0, length); + return length; + } +#endif + + int peek() override { + return _str.length() > 0 ? _str[0] : -1; + } + + void flush() override {} + + private: + String _str; +}; +} // namespace StreamUtils \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Streams/WriteBufferingStream.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Streams/WriteBufferingStream.hpp new file mode 100644 index 00000000..92dda084 --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Streams/WriteBufferingStream.hpp @@ -0,0 +1,24 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include "../Policies/ReadForwardingPolicy.hpp" +#include "../Policies/WriteBufferingPolicy.hpp" +#include "../Ports/DefaultAllocator.hpp" +#include "StreamProxy.hpp" + +namespace StreamUtils { + +template +struct BasicWriteBufferingStream + : StreamProxy> { + explicit BasicWriteBufferingStream(Stream &upstream, size_t capacity, + TAllocator allocator = TAllocator()) + : StreamProxy>( + upstream, {}, {capacity, allocator}) {} +}; + +using WriteBufferingStream = BasicWriteBufferingStream; +} // namespace StreamUtils \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Streams/WriteLoggingStream.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Streams/WriteLoggingStream.hpp new file mode 100644 index 00000000..13275aa4 --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Streams/WriteLoggingStream.hpp @@ -0,0 +1,20 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include "../Policies/ReadForwardingPolicy.hpp" +#include "../Policies/WriteLoggingPolicy.hpp" +#include "StreamProxy.hpp" + +namespace StreamUtils { + +struct WriteLoggingStream + : StreamProxy { + WriteLoggingStream(Stream &target, Print &log) + : StreamProxy( + target, ReadForwardingPolicy{}, WriteLoggingPolicy{log}) {} +}; + +} // namespace StreamUtils \ No newline at end of file diff --git a/lib/ArduinoStreamUtils/src/StreamUtils/Streams/WriteWaitingStream.hpp b/lib/ArduinoStreamUtils/src/StreamUtils/Streams/WriteWaitingStream.hpp new file mode 100644 index 00000000..f8c8d5f8 --- /dev/null +++ b/lib/ArduinoStreamUtils/src/StreamUtils/Streams/WriteWaitingStream.hpp @@ -0,0 +1,26 @@ +// StreamUtils - github.com/bblanchon/ArduinoStreamUtils +// Copyright Benoit Blanchon 2019-2021 +// MIT License + +#pragma once + +#include "../Policies/ReadForwardingPolicy.hpp" +#include "../Policies/WriteWaitingPolicy.hpp" +#include "StreamProxy.hpp" + +namespace StreamUtils { + +struct WriteWaitingStream + : StreamProxy { + WriteWaitingStream(Stream &target, Polyfills::function wait = yield) + : StreamProxy( + target, ReadForwardingPolicy{}, + WriteWaitingPolicy{Polyfills::move(wait)}) {} + + void setTimeout(unsigned long timeout) { + Stream::setTimeout(timeout); + _writer.setTimeout(timeout); + } +}; + +} // namespace StreamUtils \ No newline at end of file diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 00000000..a401a611 --- /dev/null +++ b/platformio.ini @@ -0,0 +1,29 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + + +[platformio] +default_envs = esp8266_4mb +data_dir = data_svelte + +[common_env_data] +lib_deps_external = + bblanchon/ArduinoJson + +[env:esp8266_4mb] +build_flags = -Desp8266_4mb="esp8266_4mb" +framework = arduino +board = nodemcuv2 +board_build.ldscript = eagle.flash.4m1m.ld +platform = espressif8266 @2.6.3 +monitor_filters = esp8266_exception_decoder +upload_speed = 921600 +monitor_speed = 115200 +board_build.filesystem = littlefs \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 00000000..98cfb3c7 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,66 @@ +#include "main.h" + +#include "rest.h" + +void setup() { + Serial.begin(115200); + Serial.flush(); + Serial.println(); + Serial.println(F("--------------started----------------")); + fileSystemInit(); + + Serial.println("before " + prettyBytes(ESP.getFreeHeap())); + setupESP(); + Serial.println("after " + prettyBytes(ESP.getFreeHeap())); +} + +void loop() { +} + +void setupESP() { + File file1 = seekFile("/setup.json"); //читаем первый файл из памяти стримом + File file2 = FileFS.open("/setup.json.tmp", "w"); //открыл второй файл для записи + file2.println("["); + + // WriteBufferingStream bfile2(file2, 64); //записываем стрим во второй файл для записи + // ReadBufferingStream bfile1{file1, 64}; //стримим первый файл + + DynamicJsonDocument doc(1024); + Serial.println("during " + prettyBytes(ESP.getFreeHeap())); + int i = 0; + + file1.find("["); + + do { + i++; + + deserializeJson(doc, file1); + doc["web"]["order"] = i; + serializeJsonPretty(doc, file2); + + file2.println(","); + + // DeserializationError error = + // if (error) { + // Serial.print("json error: "); + // Serial.println(error.f_str()); + // } + + Serial.println( + String(i) + ") " + + doc["type"].as() + " " + + doc["set"]["gpio"].as() + " " + + doc["web"]["order"].as()); + + } while (file1.findUntil(",", "]")); + + file2.println("]"); + + file2.close(); + + // if (cutFile("/setup.json.tmp", "/setup.json")) Serial.println("file overwrited"); + + Serial.println("-------------"); + Serial.println(readFile("/setup.json.tmp", 20000)); + Serial.println("-------------"); +} diff --git a/src/rest.cpp b/src/rest.cpp new file mode 100644 index 00000000..786d1eb9 --- /dev/null +++ b/src/rest.cpp @@ -0,0 +1,87 @@ +#include "rest.h" + +File seekFile(const String& filename, size_t position) { + String path = filepath(filename); + auto file = FileFS.open(path, "r"); + if (!file) { + Serial.println("[E] file error"); + } + file.seek(position, SeekSet); + return file; +} + +const String writeFile(const String& filename, const String& str) { + String path = filepath(filename); + auto file = FileFS.open(path, "w"); + if (!file) { + return "failed"; + } + file.print(str); + file.close(); + return "sucсess"; +} + +const String readFile(const String& filename, size_t max_size) { + String path = filepath(filename); + auto file = FileFS.open(path, "r"); + if (!file) { + return "failed"; + } + size_t size = file.size(); + if (size > max_size) { + file.close(); + return "large"; + } + String temp = file.readString(); + file.close(); + return temp; +} + +const String filepath(const String& filename) { + return filename.startsWith("/") ? filename : "/" + filename; +} + +bool fileSystemInit() { + if (!FileFS.begin()) { + Serial.println("FS Init ERROR, may be FS was not flashed"); + return false; + } + Serial.println("FS Init completed"); + return true; +} + +String prettyBytes(size_t size) { + if (size < 1024) + return String(size) + "b"; + else if (size < (1024 * 1024)) + return String(size / 1024.0) + "kB"; + else if (size < (1024 * 1024 * 1024)) + return String(size / 1024.0 / 1024.0) + "MB"; + else + return String(size / 1024.0 / 1024.0 / 1024.0) + "GB"; +} + +bool cutFile(const String& src, const String& dst) { + String srcPath = filepath(src); + String dstPath = filepath(dst); + Serial.println("cut " + srcPath + " to " + dstPath); + if (!FileFS.exists(srcPath)) { + Serial.println("not exist: " + srcPath); + return false; + } + if (FileFS.exists(dstPath)) { + FileFS.remove(dstPath); + } + auto srcFile = FileFS.open(srcPath, "r"); + auto dstFile = FileFS.open(dstPath, "w"); + + uint8_t buf[512]; + while (srcFile.available()) { + size_t len = srcFile.read(buf, 512); + dstFile.write(buf, len); + } + srcFile.close(); + dstFile.close(); + FileFS.remove(srcPath); + return true; +} \ No newline at end of file diff --git a/tools/littlefsbuilder.py b/tools/littlefsbuilder.py new file mode 100644 index 00000000..df1fb52d --- /dev/null +++ b/tools/littlefsbuilder.py @@ -0,0 +1,2 @@ +Import("env") +env.Replace( MKSPIFFSTOOL=env.get("PROJECT_DIR") + '/tools/mklittlefs.exe' ) diff --git a/tools/mklittlefs.exe b/tools/mklittlefs.exe new file mode 100644 index 0000000000000000000000000000000000000000..7de93ff28dba06997ffbf9374a5faaec74dfc0ef GIT binary patch literal 982528 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
trFs!FveeO9$*6;@L$I z2kaq;XY34O)V@=OB&G=gQFKkzTAlCDusyMoqb=eGDH~vKj^tkcRjR?$c7-RwG>e(HvpZjP{q<%Vt^Ol=TO+I0Y;vao{D9PO^x`>28nmL9_V9 zEMmx>rdG9q=?4c31gc8mgj3~^b(o0^4=L@=;W=z4L`aIx!z*T2#{1Q06Fucd@)8EU zhf$*E&~X;mZ`@uD=KEsF@ta^am;5ubJZEnU&QZ?k`Ckzh<>1P822oQyQWhCEMhL;{L!A~>e#GkY)?7H3gC;9PlmsGst@e#YZkXsMfL%j3+5L%`HetCQh zDS~M1A&4h3{ogS}1XH4c;~f{XrYU_OJI`T292hL4J;(kLoL%YGpL;R$YB$)Eq-xH( zJWREF-n7fB2+y>ua2{Z_5fk&BS+d-TX$zOHcV3$>D%z9e$^yJzvVDDoiY!3i8gnm~ zFds#)lQ_Fgy);9-;YMnUyk1Mjjh%?lhe94>MlNV z7(gT@=Sq4#XHC@r+C7gHVHu?Q$L&lrWIlivsc^PP3$$YU8vGk={2-@MswBpFha(2~ z$R?SB-Y4?n>-YWkO1#!r;w*-asc;U+x6P9osXwB!zrvhmS*g!SiIF3`2@XOQeR&%J zQi*H@v*<%by!xieZ?XN{t+LRY9uNL?2acj*2*C9aaTOc-=i zchfd+o_l<0^V~m*ZQ$0a=5Xd_STRo3nKNk@2WT9}Iv95G?j60*V-syzg; z^glp6u?S-29)ft|PBl_`#Cz8kQgvYFjGRAYxB0!0ymkKpbV(8DiapfWOL27WtV1s@ zg1G-4f;fd4<<1};RRnQh=FGf(x<}}Hj_lFdp|DPmxbvFAuH94Yn>Ka^aq<<);h_x3 z->A*HIRi2({%`R}70YgvSdlC0f1h8SCG~ppVPvsDYF^n^9M7$;5CR;gbO!iTVO~Y#G zS90_Cf;G4$lPS!WqDX4jBrq(^)dD`>)%eRhqXi%h&im3Mm%{V&fr+xHm6 zLZkAT3ZG!xLCjO^Ti>SHg9+`G$q^3JN-m>jO`jO54)mou9%92cspbFDrXR=T%Nc{& zy@$BcH}P}xr=AmN=US}%kBuui zCh*OWhzhp-a4F6{&7_%?fZV5l#lYEoyl(&gJs1`_RT;47~@77cPcUDZp9sTH#S?i@dJIJo%of#UARi0_ zcM3_SP2l9bz0#-uvb|^j8BvYtZd!?e^DTO*HZu9W_Un(yR>$Fz%W4Sl+N7p#AF18+ zxvAUfzP{Td^S8UC@*UWAP;-i~Aol(cW-tEQiYdpSp>oushDsH#ATFZJNFGLTm1g6Q zWPK>{lvKbhec1E>>H>Zol={>sCv{baM_BbS~5Acve8AB%SMFsB_81*zOoF7vtoq0;J2G*Tv81 zA${r|^Ivl7mOqq_i0ugVDT37RU(=M-c%CImzaU8sXR*Z6RVyYVxUFqrPMmihs@Oem zq&NK3d9XdP^En81zuoMfVjm=DgLwRTV!;{xI&JsIU2X>T<5vrb(=Zs~%qmJcOp>l3 zDWTQ{xu8Ia!OWB#PhMQ&$PwI$M`hiRv&Gd=-R-s)YTJCkx<>0C+rRZ-FyNxH2l z={QNcz9^|$k}fGq8X!q$7bW#D*LO#gl$&l$luSP=l=or#0lAvPd7ixCM)k~5x0Ep( zmH);_l}H7N_X!93V}P9QN9;B8`MvslE^M(jBCAa6bEl`2s|3$N)hsPb->gooUVF%D zePXpEx4il%RxgJgZ4x`u zNd18oNcTlj5+lFm#a$Swx#%C+UfV9e=@V`hxh|f;J?8Jd2!OskF7o$Ip29gT9NI3? z3eA$q8;L|K?8bUs&V1GPwd8$KZ?SE5hwy`+nT?;#xY%7n2CZLx48D#mVDiKAopL3r zAfv|5jM*0o3f5L*-Z5q!2j^@pz45N*_Rr6c-`0L(@cg*deq+g5@kBJ;R$9M;$^DFK z|9lnW=|ya$>UyJ0yp_Q&z8T4hSzHIkynSlag)!4#NsoG_soa>eY5<0lWIr+USFaha z96d>w9x4()eMQ->S=&3M=Up%Egr4aUZ!p$8;L-HO<^QJ&jfsV}L{9#0UZEYx@c%mN zq6_{`+dMWDYh?EVyoxoNk(?~G<}8x@ifG=Y`7y?TSPA%*jpXrU*PhN-Sg3Ii<8>Ti zCfXKTA!|3Dp&aA-?!1xgMIZX;bKPw68#15#x+@4xOkcJ0xJe?7xLr?zheP0zi$XkC z{H1$FkNQ!F?lIiLC?Ui4%!3lypmB36@+pz+Wz7B=z!KTs9L6FNO9hy)q*5=Ri8D4* zN66E*CL{F?^_``ac>&HGN?y#OOjEs)>MhCXs&~I9u`zzY$fCOtjZeKG6&9|$7%2-9 z?s!?DXgOh7X|-B<5d*?~gb|{`RMI0R0Y#qa@JObY@$t%h#&)4F)Z250lR%frX#hg0lS?y9#9udDv)tkmhT~m@5?T<9YWlW zANRr0w5$C1xZ-Q=4){8%4d+9C8-Yy6Wi>`I?VcI?Idk?{_D6Bm!zR%*>8dNwm5~bb z2zNV!?~r+@fM%#U-?>+1`Hsp`>Sg)#b`r68T-gAfpOoLquyJ2vpF1(6Wf;-oC1)@( zA|$9Ze(gAfDXzB$V&Q)TmBZXUsSf+<$($(>X zqNxUG*9c+)P1Xg{G1deU9}Er?hu)IP#wUHPffSWv@wA*nw&POr2WhplUPO7~{c5xE zi})ShiiP_twhoJA%g;Rg0Z43?k-AcBajj;I)B#km3x1~DLiN#F#~ke*LTv>R`iCpn zs5zw)^U=J;>lgXhUeo}E!5JqlE zB5-n%c!Xo+xQt^?D7ZTFaF~rE6r6Yunkz`N!uGwbaI|ULCO?Et-#E-1&iU#4$XohU zI`H($h}e?BHJO3zE`xO|&Ecaq8_7=@H<89Q@pr?+@0}onBKu2)HkbA_ zt(D{Ky@~pc|Hbs~sY`1@%tR9_FZl(}GSDl-R(GNYFZrJk$@$GZ!zC_`eIEz(bC`4Lh(S7R&i|Gw5++CPo?=B0{gLv(&+kW;U9s zt0~6v!6eeIu?zLdH?#4>_?wU`_NqGO18b|p|3rX|s3?xQRAeC2UndE6Z@m8YxDRh{lL=Wv@g z9nY8as9(ygPPTWeS=~AAmJcQWD1Q@4L1>R#!bsW4ryF3yJfq!0bqVgj#V%A!(cB@y z`h>ffEpgndE7Ighv@4^J3!@LhpQq)|-g-%jV`qE;^o}QcUb&OfDN3vEQqC#&$R0t4 zONV721Al%X=g4X=eieHqA`B}wTU9gcfQLd+PmF!%CX^u%^jRB5Dk%GoyiFEo0Yb^m zL<0u?dXA%tu%KRbQ7CNW&b9JzL+y@##-hw`8$p7s3Kl6lPV{TOZT zsc;8Lz1k>rf2nyb)hbd9QKyX7$#dwqNx~}}!Q}R)2i5woIv=Wmu2uJ5FOK!$0Kwo`c9DBGiCTprgRx`VPW{K`nX8_80t2Ql_;C!|SFtBf}Y0Z`# ziA&2cQ&lAe=QG!aY|5^(xPpFJ!YSoc4P(x2YI0b$?-@!U-|PwGqt^>J*s}uE6-{5> zYd7PIS<%+Eo=mo)jhk|t6^;<7{?zB9Km%$32zb<`3 zc^)}@T)P+mwo@P1TGWrmN$z>hdu;Hg{eq75sIdnDet+ixPb>o1Yj@*8wri_@pEYN2 z2p1{(7q?a9|EvYl>oSVsM=hHrzqqMp4JJ*Q_}SYf%ae# z`ZuBFzHa?`vb;khM+vC5&*fx@#5)|M^RSHZ-EPPZ03E62|B;!~d_Mb{llH@Pg@^m6 zs;RTVW&NUk0V}tG+F10h3RXDEEy(=!4aJLKn(bP1zVFrOw_6bWb+# zru^8BfAjwR!^I}w3IC>#9tj$G;njE7+%-iqD^eh{j)TQ^EVF2NI+_DX+jn9w(it=&b2+74j8k#hXejsRkJxjmSlTAqpMQHJ2Lx$H6# ziyz~~%oqAyu-}S2)Kd0KW7an{^>V}I*d<@fuk>aAP?5;#5YdpiGrxZ(G{HMTvYIKl zXN@e4TF=MEhq#}bVB9er+n^@|nZD>F4oJjfP_M#v`zGA|6WjKlabOfb$O~_Nwy1Y~ z8FS8UaBBP8doh0ymbFzZ?s6s^;_Rc9`NJmEIX|0a&eJBkcRM%h9I|^v^`^#(wmG6M zEA>eil_>0lEKmnM4ag%d1^o1#8YUnH$yB@0Nou4X;bUQc<+nW)bz5-&^`sBg;0G|Z-R{+QcP}TV2nQbB26_Fo99J+V&2mUB-ZhKF)k=!?@ z9pbo`Jqsq0vtV)^>UCk!I!+~J^Q$`7h}3&)tV&Lt{jb`$8YHD z0t>uZ+x^@!9kmvRt>@wwWiGczb8=980wApS{4t$hV&nzb`!bIHV}7ycP5W_SQYq6tTb|^f z6#w8Oyz5(*;F(t$OnbK2o^1D(?_aWjW_w*ZTe_0s*}Ih~I$I6I+@QHzs#sRm>zHM; zWgKwW&1evqSZVi5Y#S5rr>^GY4*Cl2X>Yw&QJFh7*7@xRp&hr!OdtJHv#dD9a|-7_ z;-i>I4jmB%tC|&_J9OVj!54ePIHuGpbUVQ~exu&~xeo0Q)14h?!QZx#E(v5Z0Vm4svU4us=(waEGII98gG zF7fmIT3-c$uTX1l+^Dr71iZf@mscsF^Frc`(BogPS$&%$|9#v3R#~!7G#nY9p^n+c z3yzPOUC-0A&V+!JDwS1>W>i7+T;k+#p=FEY|i z%?0OI6`fzmoz>Dm)=Tu9X;*R{;z@0+1r~V1Lf;FWzc|8C-1P+b%~U6(gc2tYk5`Pd zudEzr-&A2g+JJWH#%qofhkxq4?K6y|BRc2S*ZAAS`RQ+iVbe4t^(60mg3Mf$uF1^M z_bHt|;ol?s1)Kw6UvW08eVpHeO$mDQkG1G)m!Q;On!Tr8-*S>AtK+e>+s* z#QgYMU*As;;!p(Iz1RrtFmsWr9Ej}ei2QhRnp88rHs6$2ZtM+I@@O~ZZxjAvMHrx zhZFIWTX7XNMU3QTexsJ_G`s{*Yf&T}F6aI7--Xj27h6SGm(}42_Ia97~iCkF6#VSYe z`R|0C2h6VbOU=Z_&CzrdE79cpk(TX$*Msgg5qP&q!+#aB!G&`Z~?@LvsPmG)0`2BJj>D zxQ{~mDgH8e1-}!2K|n?lOG}gyqe=Op)Av`|?3R{JPSag#B+CV3yJb0j6KFcdNPR*Z zd2SA-TV-pOF?%yTE~jZb{K^dm+`J=Y$Ws}$y4rh=i65W4V{S&BG4J#elgka$V^_Ct z>oa2@@}316^6G@?A5y@yx?<1TE$fE2yuzvf%Z>Ck!c>!2N9(#|F-IM1eAepJilURa zTh?*eE&0B594~iwavU#=y>J|;3-Nw^sVtN&bq_vo;&;?(2S#c%Qoy}MBu#qU z@0`i3l?%CPJZ*=@sry9|OXT%EJU}|B)wF(I3$e}al$v1zVp(c12|)UXnZ8>>L(KH8 z(mPS>A(?fOQzn!0k4Mom-eYtA(ab-#<{u~KA1}y1PRTz`^&Xu^XrVbdB~b0VV*Fho zUjR4L%VGR>H4A9Q_$~sa%IJYgOJ#F@OH3_8Ahp(7Zk)C#s+P#^@$)#h^L3#NI5Hb+q!?;=|-V= z-af16ex)1wvhY}nV64wJMVgJfx)a?c?#IX{^bueCa|iW2nP_Y79Zp+G)1!Y8%%2*rprX}H!RNCmIo3{`E^dju$JSM$*!-DVXf0N?MXGm2v4{iAID!bS)L)YI*Ls#(D;{dFIYqB z@ZPHU)mKK@zGhl4%SdWi%Lj=__}F=fN0mVZ2FXMP?7cIwbBz$o4Pd5ySxs~Nzf?}; zYf5|`B-X1t$grZ>ZW%wh_5w#W(;2ORu?d4lWPXzW&Jn}5X?-g*TkA_zYbP~PAN5Fm zPwDPWs!QZbG9lZy=|5MNNQ;zLDfydEJ?UUI!Q6iuYws*-1RkF5x9|y5Y;~*)Ni#uFzdUy7NvfxdB|$Bab4^%nfiUk4f5sO8%AGN z9kLy4WgnUgpFOX7ZJR{?&p*yXJhLiKpR?!?>n(b9DghHvxar*ifo#={f`vWg2uP*Q zeLZWP@JiK{lI&fr`hYY%-Lf$!R`Bwf_F3!dky}axwV?cjVbkS~>+y27YJ{q|{CS{A z#uLWz&|Ut(`vU@f$^xC;&LCxs$Nn}dcX&`C9+lZ zykx76>%UiEp;Ur>kgfWZH)rniRAjddrn2*Z1Gcbc`A1;m&Xm5Ff5Oj+p3>NU4Cg`f z6=>o1oIG%z>K_Q?)Ajv(G6Dvj#=V#gC9z)J0qhD6o-K(D;-c(Wb1t*oYe@eO>2ri2 zdqRo)G`<#l-Ly*~VyuL5lUNLEzZTzASjLF}F|)vvH{0_g_)Y}+2|^qy8Tye?nXG9C z0c3r(Vm8?vUi*_cQ)DhPfHtNDP7bF^c8FRXTVlzOi{w&_0{QcrX=7#zOJ_;L^p=#5k5B#m}US7v@tfty|_}M_9!>9!)ceB(gu(f~%;h9ho8ioOp zxmV3Mhny?VxB2ooJ!Apz?)7x2(jVy|v*aCv*nf}6`=tE)Gx8qw7+}LcipRTYEibx{ zP~Np9(0>(u8pNoXC{i6-Pn7tf3q%P`(UQ4~#=x9^08ErthvqXO2q+WUnZ8Oml4}{A zpQ%jH#CjPYigVaX)bKjKc5K%_1*;nXeBK^#{(CZ*h%s79i0!SPQ#I@`oUQSP&+9V!(y6 zykLh14|W>lV|qZntN~abId!~3_9Tb|0!8!L#C!C#p3K@q-b943+B48hUo^jH^3+|v zm`@@)oNow8?_p}#F0)5YNJ1HG-0aL4Am+qj)n^0HKA}#2>XvkucTi0Jd2___xx7cM zje0|4?;G#NPUv^zF=2ZzIW{}B9}qaRv`hu)_2tDLU%`6rp=^olpC8ZmzH%5+hFZxg zp?n+V@$3pjlaYFrQj$H|__mQa9;!v{qc;!%%o4~W5i#zROUoW#X7@7Yoel3e(V&~? z!3~B@{7niQvtJ=oLRJAM^P1>sX3v;ZYBxsN+fmmY32u#ApGB>fM?&+(Vs@Iahdhwe z9%lU7<-b7wa&xSG*sR{t$2V2Gzor9x`YRq?Ry%yi46+&N+oY?|dZp!;@*cJc!tab> zA2q-1p)n$Su2Gk$+Z!~T#H!}39hD{x_LA^;G9Pt?m?=!<{#t9f})gd zOYNwvXbk5NDZ5b`m>zK6NU-j1d4%+UbB~(J==yB0x^aDac34xH6nKdOYQdJ8HjKb2 zcs2=IUWtCQgClMa1UhF-9+Bqf>gz_(Gx!7sNQchZKm{Yfr=2t5ZU7M@3IWXcb%Ec}k!3u0=yqV&v!MA$FjpSVF7DafC`XJ~i z(Vx@bBZf%%?h&`h&o^>J3w z2Ku`~aMKd?{SA6orz%Rmq*LV{GM`>K`?A`(U!pdxrFQv!Z;QnmKTOsSs_D|1xXZfB zXOpUU?kDBC$@}=N$%Q7MztNS~yd$$;p@fB^p^$g*8W%}{jD=UDV5D5Tg^JIwCt|1M*rJ7bE}#7qB?f0=#F?V zg+qL)nhwhT4(lVOxkOT@2W$a$wrW`EUIF(UA)J8B{8RL^cc1D&l&8&-<}8Bcv(>Y< zjUIgpGUau$DONS+B;`@q)kwfm0gq*u^El&Vk6oXVbZF5TKiOy1>5D;2vFh=fRbw?C zo6ZN0OdYstRk4JT{D~A-Y!OFO)V!q)Dtt=%qB}FYQSkNnSu)RHsVn&CGx|e|bc14J)wq*MkH_c* z#eGC(dYO&X4DuCl>-l_8`Q4rzLn<~Mllvf?D(x-c*1FusCAm^+@K*A{Sxo$my5P6a zI_l`~*T-Oj+Z7Ys89*=FLw>-E1WO!e+DA_fO45k?`O;zc%l_SD;`0&=zZc9TR~-^i zK=c8#%I|WL!3H*Tr3bV~amJYFex_`XQgtEm@bWKS zIGBD>J}&2SP=}ICdSfH3TGQen{PX~azRy;@D>H7p{3&6C^nky~k2T~s>Sr_-$#6gx z)cG_N&Srkvq4?P@Pm|AD1zbZ35p|(&J5Z%6i*MN~TUS3xDuhK2uqzv1i1*8!Y7cCD zIaY_6i9(~fE8_1{0q(10@_`EGOr@$^jKy7G$2Asw(3zSw4uYf7g6TMAik&HCWo5LU{`(Ttj5+B zVygSJ&AMiAzY^`b5KDem3l27(jP#;ObqSZ5 zPJb(!jxMha+s*wWjcER^85N~mH{ZB&T0b$Qqm9es|ETZ$cyWRM&RcKARCn&@_7sFs z#f(c#yDCNyY5j(_Sto%_KkaZ4v5oI#jnhDDpnz*m9z|PL8&9@^%cM$hY3@b(k;*Mn z*3Rh+i)*!6h1m+7ofCmThTkEvYt6=g#ozRuf~soOpQ8Vx_JB_b=B!^Ev6i=m6hRna z%s$as+$HYJr~9eqy$H0Rkj%CA7gip~2+zvETzaE&?As3m!efTyG`F$ovj^)M|VbJ;KJ5;Yw~8*BN9z zK(*(v|I{7_7Rj~e%PpzN(KEcLcFU+Es$+fduEzcnk#tZu8yCf$JnGx=i-KSN5FxAe zYawQX?oZuH@P~fR6a_zGw5-lGKObM9%U>kr5ko7zD%gpv+b`v7bNDxNQp6e-HX^~! z5FOHsKh67K2d2Y;!Ljc8`n&$EZ|iI#%`|pY;~7& zum_V-pYKmi-yusQ(myLgieZM+Ms}GlSNWsx-5e}eP2f|TqQUj{tPTb&2^)+jPlt6U z4D^T5%G@xTuo4u36t>5L-*VUv{KkUc@@WTYepws;2M~QQzP^kG*Tn~A-p=t)NOe-@ zKDJqgtn7`sg5#!}S;0+jg>AF{xZrxf1}z^>Z#747?=EsUS~Gas{^l%Ky(Rht%%jzl z`a0tsv#!e|apJi}vwi$S3(xZYiHKg~i1TgXao#^EL~55?BFTrWF&7%%KSYxH;*@dO zvMLkw{+Y&a7nxm7g9pd+C#YCmOe-35v>k+$NG!7m^m zxTNHxe4Ve>ApH$Z%>f%>e8LCY5UFe9e}*6HS7cwbOQ~>6<~PF03Lc8@dDD6!TlFFt zaYo1X#|>e1S-qr(@vY85uK4~Hm4eFscq{WU?H|roJuCn^W4#rUwTq<~Syq!p*Ke)L zRxRSqS}#7$c5QQ-=K|WVs+Yak%rv{~8g%hz~WJ-eF=sE6JWUG!K0~gcF z;87YE{xWSgLuU^w=ZoSuZEsquHYb9?_};!YE#P9w^=ArU{J zdvuv#WnHZV5M2T_>pQ(v!A>#YQNaZHbZ2`BK7k}dA$*_S*{VNm!FpM1b*aWcuR*EK znTIrauur%Y(^;hDB%k7y0u7OL*K+DWx*Wpd35ohbOn(QGykb6u7mTl zRri5O5ycDiwbn-w47|0Ude&BMtwSUT-y7=JhIDh~SZt(iP4fcgE8omkolM2*JOsUf zYPSx{tqDZL4#N?P>trFvR0guGmGcop7z9fg`uYAA_?Txi21s0i9bO|{TD#bJj)-o2 zQqAPgf5*}2fB#MHyB#c|KNqoA6xGkWJ`F%~s>ubk+kLNQ&8C5`?Vv|3-tox@zUB`{SBU%3!`3 zseKqCKEF@#y^o$f8L>myGp9G0R)@GEGB26KFR6{MA-S34VN@sXVX9qD+^+)4y!aCq ziB(+^a2ed^;7DI5-cnQ^;ArTXVfBmxB#Q1Oc4`Xd&MtNUjztkU`noM zIk}v@3 zOiL{Z9?Vzg`9#Tsol`_ng7HmYO=y@qS~(E` z8d682Fy~mQxj(zHaQyq>TOOTedUSe1aeu^mUeh5xsb*@#`nyQCW|~f$Ka*!uJ@e2i z4?-Pia-YzIw2A)53;c(v{=-aph^BuajJVB&_U)3uKY5m6B>&{OLqDqLdkUWKD|mjS z;Q4n2&-1-!s#)!IZMN#6&7wFbv9x6UxkhRle2E7xp4@l%#b zS(P$n5QPPqYG>o&X!b>kH+v^~dd)bBne@rEc$p{fbGsQ_nSE2whXEZ$I+(Lo1p=FI zV(q8e?DN7S6`W~ot+O^@s8-ATy`3N*J}_HV>xq>zH&VM#~zsa?{5KO=zMC6Bi3knb&gq z3*4jW+wnFBc;c|C{lr6gQ=rev%tA$f>g&8I^LQ?Q{o2g^xz5n`f~jng#I|f~#N24j zh&K9Sr1LCHCgNu#+#?ZB0ln``Vmd}>Q=xic1BOx2f{?XS87t0}j|(=P*!D(pEeJ)^bHg%*(pLmo zu49geoO6yPQ~Ohrjt1_EeZ!cSJZ>~^P2Y;`-83n-S7`nuKDD)2Wdzi}KmSNx#K>(6 zkc*!oS=Vus zxzJDs)Z+RL&L)uR4sh_s_TQWr_)-snrMAp6XNk(lx+hEW&Xek8FL`;)<1ep&CJL6; zD(5b86!cGqWLsf|{WzVs+qPg2rJVO46;mTC5tZWnLZw!l>8oqZ^f@R; z-EALQrJ_ku8O(QhKv9b1P|8FX-+Z=tZeOG!igtJ7}OW8xjwB(}}!l1}b&OX=fm)j2GjCbsS!o92}lA^aWoX-{^yX>EUF21LEu``3_{K21ZIR_S1 zf3(kBQhB-anvi-&g9RDG#7lkm>ff}J`k#_2a_INhyyX@DCEs*$CHgW>9>3gt)OyjG zSyaWe#24A?FNhJ(yWaUW?aK^Q{lVKKxR+#>(K!5%Lg$+@fRTBo`tA)H-L77$U0?3! zT!Da7s&Er8Nyc@y{Mtt}^NN<*3O}MPaV&^S#7N3mz#Yhq2|O6{_72%WFHR9T9!+wm zwv63`wjP4hZ)(53CK@nO50WU8lLj&Ohha=~kdUqVoV7SwaxQ@foZN&;X^7xGH)emx zS6>sTc}HjgEk!`CQ(7Qb{y9}%&w}Pl4q8S4L_R(VqeLKKXmGJwX`9t+zKkL-7nU}n zw6o2UJua$P$J_}u<9xsxrWoASqKBfHL|cdd%|l8m`w~(Ox={xzP?-mnUqEV?{t;ht zHq+tU0+qT0BFq&_xYv;kf}y!VmBS?jS*zqv1or` zGl!3!Obysw5$i?St6;P+Ut8vU0}#aMfz2z&b0=PEi-H`R7C~X+qWd9X-myz?wx_mm ztXGS9j|9of4usNu&ER4px+2!T(~rTv6THHl*Hs;1!yu}B~%wMpl-m32mS({iqpoJr4_BArIU(WuZi9TF_=HXe}RLu2<&Vd@< zOPOD&`aF7@n%-=HF7G*x01ycRRisuFJ{X%C;#RK`;t~`Ttr_W4n5}qFm`VFbPL~W* zp|Gj6gdR`Z%_r8;GFf>3u`+s?PdK9=rmZ#*KO}5^E~CP^<{{j|=_wF!X=L(q&H-c~ zCMnZj;mh9GApa3ky_fY@XJ5WSvv)av9h>9UP33_=dR(wMnT=pt1taM(!O+~MnsMpy zUcva_%z^MfzHef$P3*`;!Q_g$W08i@#?H92tVH%8l+`pHZ6r;&T!O^kYC5V7>u{{9 z41PRQjPR>wIiSByYH8 zDjC?a7XF+q95UmaDv2{Cu`SG0q+K59-)Um`jWumeFQ(~|IMrEpvK&9TDb_n$_f9xH zbYz%S_E}4WxOskIY?Z3Q@in>O!*X*wH2!hZIKjvS&85Fo5K2sozk)fvX(b z=;7BUdQbI%UPtm%>zxv?7nIgyIUBHSmg(s4a3oU1qv{=uwV_}Z$wlJQgJ#7+C zbs=wZVO%tQ7Dv>+3L%)lz!XpJ-D0f@B~}Fq-Dxq*Rl&6(i(@-i1w&i9Nu{vlvpYFg$#40;I~%ZHF|3!3AaRHcY4V<&edd8zZ>ON8G2J>Xsh#`MzHIQ zXv;X#eTvLF0yO3n)|Dk^oAtArUy$U~lTnU*6=Ibri2h2GqwfD4u%`jpir$jl=^#ZOK zq-fZ_tmd+oK1;)O|4MBzZf$4!*v9FKF%;~fbeG@xV(m^9r`Al>yC@(_t3eSoLTflP zsk@<5sD4Uirk`i8L%)EvQcuZLse}Dao z*WFjwU0V|4+!*w5+hN(8+D2q=B1%eSr(Y#Dz3bg-nfqsN>a&O_LFJBW4meleFNVQ4 zf$knjn#T@Byx@L{&T6rihWo({MoK2Rgn7!UE=Ov*qk42JZyuprdD{WNfEOl)7L0G! zR#hvthw8d$cQq-p&@5ybh!B+)PXkyqEE9b))Os2Gys~q}b>4=8?rV_qtJJ%I%&K1M zkU3L8>NnF#?N{QZ6)n4DKMAv}`VBuwsY#)yRmRtRj*eRF9?(2&m)|Fua5hm@rm`i- z3?b7$H0z5%CAM%?m>8K1JOAJ_F>?+YMJ56w)n%C57*3NM>v#xrea*zI@no{*4^$E+ zIcg-u<|ID1rZps61K)K{ZvfTBq4ew$)PUgZzC(+Rr%wrrQWVBw{uxwG!=|H*1@WW zHIx!;hG9EFEv*!Jm`UB}zn|5#^V^czVkAG=3Mz%VF!YpCrD#M^K-08de4Hk@lVFUT zJw#=fQHQ7!cL_0ly1{j-!5blrYH$`#Pz{#2IOFM2vg{vp4~CCuaS0SCQEJRBmlW7x zKSqQMsM~%E{&|*wuQZp{3c~?eahnu)xXELQ)CPfLB+~%0)_t~a*1rOqv)Svf!QS#1 zgklo`0~{E|T}wWnSWM`OM<^PE%6CAxuqPNaQGSB-5&zq;76}C&3Cix$&wTytgT+p` zeIE>Mj7O_m!FEnRR^Sz|jMfLCx@FG8$1$MNvxROxzS6Gw(wMq+)?)Vn@cwLr8)j@4 z&mVO>4Dx`@q_T%efQfxBt*EAf$~dZ(2d9gZj!m{}RbW(NMOz1NVAX@*-womklN~j( zWbVaF&U&x4L^_#K^ZbH9GfZPGaW>vdl0+ckn^;1PZ3%9_PM2RI?KHycRH)+5Rr*Lt zzeuNVR=1wW3bS^!^sr9xvYNF`#_T|yEfclgQ^vSq#w391F0)>3p+)g zCBbcjCzL%mOVfh2FFpxyy$>Haz%X8xZnz7o`ESe%Gzfy3X%C6x$!CaPxbQmH^}}z*+o7h zcR%D~SwClgrN%yVu2RTIG$w%#=bz_(pLbawnMkw;0YPG_Pl*p|lT-3%oH2J`ZsH3~ zaXyRTN`Eo8h0~(YOmW&UdC;y`QJ_pAS)P$q>TFft^{Ue=7&%eqcz<$M0Iy&QD^tnE z9X>x(?kFaxCg;7rsF=$x|NY@mpZQaR^SV&%$rnRJEpg;8T`Tsnnkp$aE?h4!nvV{{ z4wq#XYt=Z|`6txEaIwoLQgOtNOXcveT^lI({bCKM^Y)$h@BS-$c4wgk%A^ z3oO$u`RUZ4K733w+AOcVD2m1<{9(=_OR6F|V#UxhWQPu4OWbPssd# zoV^QtRMoZkp9vF4AaDW(jn@aUjWr`kleV^upv@r@oWTKksraa+H#WsbTa_80UI@XN zfgHzywAx;Ozj|A5Yj17qZENcV1Rt3Ml7NbVROR7=hmUg@kcSl#2;}!&`Ou65a{E*2%P3UjlvG6#7nm6RF0l(PA8 zWIIzQK-wEVx{Z6x; zIu)llhWFuZf`})A{`Tk>le0?k@-Tal~eDwxRiFzx7fMbyHS=g1$WzAk0G18j;ds`a9it(Hs$bZTe-(Jo=C^=o?z1bj zZTRMk{NfA-!2;Q^83V3d=O5^1zT}kct~RYvYzgf7D|Bk{w*VQdr^h1m7-ZF^== z^t;bAXz@K{BXXHiJ@%66862@QlDSs35I@a(IC=GoQaHRPjhRnRpU%^bgVSmc>9G&S zgpz|p=??}^O?u7A4NFS%bF@ZP8uj0|)xxqv;k*!qby>QEiexw5?%jD{!si{J7BVBNzqMyp`4HJfHkGBY);93^^k)sEXS6^7F#oK{U z6c6iVUeuECm|N*;DpfUxmiOe)+M@FqTKxriy*wWq1YST9Z@Bh`GR8xV#qmICzE_J! zs44RY^C^nk^yWZsb@IW+uPa|nG2)??mmhZUqatvXL1)Uiq)1Jj-ByD?vz#C zmt>t$UGhvr^sAS1rWpVVB3H|YODL@~7RE`)e#eigN;Z4@&*>|16x-7av(CIB^xj@w zCf&%iIQXQJ%H!s7>JNI_GmkZy`>AB$WMP}TYJk3I@84AJ#&X*u&2i zi@gPKLBD*(72Ok?w5u=jrKESKzb$&$eAq6we<&*QoLyd{2>MW7gKE4|Qx3(tM=t#G z@=sn=m>98elsO{{l9ScEPNfJZb5q8t6!A@PQu_4?r4^K$|J)=D#2FD(G^8C_UB6qG zg<6l1&Hces^>>23AA~<{>NXWcqf{=}G14ga#tze%o|EHmHFjw?cH*?x<~?6PKt)yi zA3~+s+)$Z$JGq_j(v0laRls&a_;sUyOIC8JC5%}iS!+AFheS#cr@@7%JX{;XuNodN znqdbN-rD(EpEUq%Z1ktq(P)S?2UW*3E1-h>Q+Id7GG+bMB9zTd#ishwU7 zRGHr^{(8$<92RYXASRncuuc+Po5Scuo@vHwW^p6@69qhw9M7Txbz)M$cfiw0Xc-bg z%p`5q13;z6PG7CXPw<9qY#oimKP?{4uiVI8hUlN1;CfVN0xezwR?{T35l7DdDlDwVo#D2D6$S`Z#JEvcDA+1akWH0QCB*s(GGPA&chZE(}r z;N-uQ#6I>YH#r4~6g7WEl>!MvBmK7|NGtkw7Sg{wV)r7NR|)$PGlX=*yw@p~yxHq- zS#}pkef=$3>R}3t-L9D@v_O9FI;lNJydupbDVg$L;I+NFn7^vo%q%q@1O15P)N$n7 zVnUb?*mj%JM*Wkzp`2*2ebo40U%J$7$El3A+H+=5tI=uH$mU@{WYb>T;$ z{>nUF%oef5I(00NIuyQwwQ@oOPLe8Y%8ZipG>U)FGPiK07ZxQ62Oc?q+qzMt_?aCw zrN4c}zf!l2;dDd#RNll}^Oyazv5hGc+czdsjdUgaPl*vMW(I}eQX%B>=nzfl@F;16 zga;4P!mt-gfzh{3@0#Ch^EyX9{X_W&n=|i0yW`AUL?y^Q^$@-0ccxMG=bZh)h62b!SPMYK8(Xu1<%j0F@XhX`AWkvSO(`CW}NO`tQ++uinzO0Iux*F|p zGiDOmTX=@wVNA1sR?kPvgpkY!818Tq)7D<|j|?eRvk;6^=$5Z|A3jNo@=qdRI7VSo zg_+5}pz&N)6hYbg0(b5cV|p?O}i3u8+;KY{?+Z zPyho6yV}TC-8XkpEzn3K!K9rvXbgdHStLjDV~;8T{NdJo3T^cvbR&-hEzcBlw*Vqd=PVJ zSa5IM4t2|V4sNR`g*1kgk2Ji8{(p#Q(l@BqVH+9oZl~L~}ub zS2sFY7`)G#tVT=7`i98d1zQ3IyNK&fb-|=fjoj7V-;I}V@-&=e2cUD5th3o0_;{%5 z_&ljFz@^#L2BP*BfvA%e(aVCcT)|?PSh_M>9E=vh2s~tnWCE$FZ=m()v7UTw<-a+V z^p|>wcpv!Vs4Wd=#9yA}p%4ub74WOmQd4u?_2Bgkh&yW2j5lN)r=$Xaq`f?|FOZr= z5q2VWhQ|_ACKT(-*H+)chK0Vodko5!wt6Z*0?8$1^J2$OD%hzd?w6P3ePu!RFA+=9lC z0NbhnwOfn7PN@K6Amp$104^TU&m2e>hE$Ub7Na`ttBCFCHqx<o@!fB%fAoyRgY7Z+n%}G}^05s2Zp~FutKB9v#jS zT|{aBe2;ZhIqjPv_>Zloxb0_t+$0DRgeeE(8lUMPiWIg0HYH~jLN-AxAjY#Xk z8KgmUtFFtJFQ?DfRv+NEbclvAAeQFfYgD^wa?;xXjPY&2Q@Qz5?ZqwYuO@?!`!k0h zDw6m~gnW`Bo=(BSeX;JVk4*-OwDybUaLVD)Ynp>8TO}5+jdEFQRPetv3uV}0^$NED z=pxp3opih7o=d@FHUo+6^JV-_po~K_E9@(cQ(s?R( zVKJ6{b6UzfGO>Fg2f*NuFu{|2mGm@j%YXeXgd_u;nC5wgFhexzT zIY4KT8caQ2_B8DZCZFPI-t#YNjO2aLv+9#DWD$zG@pb^d5yl1r(+prvU(&kK@#%s&y?sf+`T{(zMgobAq@> zRf@<94_}O30jz4Jx^c9I$V6Dd{{T8N~m;8!f)?|@M#!+LJ zIY=~fqm7nkps5T86r;ubK|~Tc{(HQB1S#AvZSF=ZR!N8IM<~CS17-(FYL}CtA$~0j zrr6?Q#2X%DOTk?rc`r-lom>I}c&K+L&3~mWkaeFdB{N>dtyk~oX>Q{P)x(eTARAr0Zxl$`^s*y)2|Le5!Gobw`UYYtr2(lv{@U-au zHqdcb{jlSLd*TJdCWIOvCyP+y6Fe#0X6{(5ib^_~Caj|IcVv~Y0%`LdFhXJ&7AzzR zcq(AgUM>C?x@r!$d&aWdCSeYoOw;LVF^koSGm4j93|nJ5d$pIsP5C-&*T?f?N5{zW zb<5H+p&!3Z9$Ml|{S?5^lU z5u(TJEN_f$XfuPA%xur zgtY(;8P@$jlxL5*nmih7z1f`fX(5h;uu(5#;P1rSS7)vFm zjTeGTnLn1y43Xf`F4RVZhGNjuZwk4IGRY^MQG8krs&bexBlI1b^JH}2L1c2{(}DxU zxQz7-T=+@?p8aF*;G7INrOgJ~V5<4{__laj{5y%RtEcYB%fVBFm2trTsW!b8l0z_U zN;KE!`&!wQ*L|C$LMFpi^q0x-od^sJR^hs~*q^7MpTqx+tySD&y;5Ll7 zaL{0B6McD}zQn8|c0UQBtTix6j(k&3o5hfveA1Wi>0<(pIsmkYhPi;#7$vTbE*Ek7LdG+fGIgh&DZl{MnYR&+r{l_ES*o0csRt98_%~q`iDouH^+GD6?APdhj5c zt+)?XL3;iAF1o0z@|wu3H;{Gt+KQ*h4`-;U{J`b0*7p#(TKr}J@EpJbW_u87tA46j zzr6r64qz5o_FYEk%Va})IDpvG9v!4yTt>e|Cr=Pe(doQMxjVfCQMHwUY_D9EfQC!y z6ZC;r01@5Cv)np>lSq?ndqm+TTKvleGLh6S&04rdT|gV(sXbhXE3ELm#9Rovl1++U z&oYN$B`#uFz>F3dmuTtM3mOVLY0^UOCanO$6%P& zjJ&W%H3u03q0(IQ6p~fu`KLT(o@eAYv*>H97`0AApacY6KzFl>Fkl&tx8_C zjLuK#W76GuY^SA)SIS0+|Lq0XzXzCHS{5CN?H!}VuLHPsrBvkWFw$N3^jcR5O^o53 zFi1sLcNsQ(TAw(ltaxeZf6uA4Rf0@-8)j@Rz6%iwTu)KAh|feXn+fj=I)c^| z^4t8|=1+{mJl*%6b&Du@iu@5R%V8q0cNY?vzo6E`Q&b3#`JH>@`(E=_o>>&WRtuVF zf*kCtS+i`w=KCMFwG5j;1%X;z?pP7sd5o*>Lz#j4i8aX?C8&V1s~=!B6S>q{Nk*4` zL|741q*sMV{DIV9mg-yz73&_eY(JZj^_Q`ghWDiDxiJ-frbJJU6otC$fWJdGMrx}i zJR!)D4iI^TT;N7}C5;&^4x-EQ zY@&Q4m2ooTkW8P$+6oP19@ECPvz9o1OuO<3*qC2>09y||EMUrk6RvqoQRG96o-OFDCmeX(ZZTOtsNp~iri!*7gpW)|b z&kxsJhh4zh&n-<1UO2miO4-&UcPSO-xAdMtE;YMumVq4%LS%7`QY4qp8@9E@@*h;4 z9^b3Qie=e#$l8T?ab~T|P@%`MJY(HbTab-qXNGI5X2U7nAu!*Sx#u9A;zG(&%&rtvz@qOz6>Hqxyf9b#KY9K}WzmCnNa`Wd4eQPkY0nE$;BRSSZGBsFQ z395wSfhwUP>l76!fhPg~85kYD?4r|pPg+UYLpo5nN(E;N*Ax`A{&JFKwd&ia``^@7 zPZ1JGj=m5W*J<*}x2Io(*DA{b>qX0#4%UJdmaW#60BLN=nlY^+=*FdIS3v!vwZ-^` z^#z&b{idk5K9Qi>JI!C99E&OHqEF=W=oULU2uptkcsypk`W`I^GNV+AJ1-o`2eT5% zCsSkrJW^jLRyFGnKxOkp#STY0G;fs(iXT9 zh10}kWYkuF$OINu*pOGneAT~If1&#Wgta?< zjCa7DIzD@9^fUcYA4|11|FXU-?c+r)uE`t3&b31lIJ+*Y%kL$jodkhDK8wK@CrOqk zU{*HY>GMbLL`JR0;M>==ZgsV=fO5{nurFG|3n9pVWPX+S( z=$zsPzXX2W^Cn?vBI5&|y_un@udVSM*Zr?W#^|w)9z8Xu3{^M3-ZWZL-UdREVIvE& zM9E;i>CKnV9&ObXG(f0eT*18!ddgcRy%my?J@da=6Cg0R|LzO}U zIG`F+>v(45k2>9bHuPjwiTK+5CzU9wx*qVPj);MfA3zGa zQB`8CB-em6YZ1R)xWXQQ;~FCEErm1E6fPO@dUIQ+vFNB z?5FiSU!)yTLtS|!GrsV4g<&BBcP={<_%D{-!AIz*@Ddq3vAG9QciUUl9R0Ht_(<>X zz7Lo)iXF;!r-=lllO69sQ;D(V#4h#(V@JmLTOvbQ2{{(90bQ_)gq7ONQ&47opIJH} zK2Ujba`vgXy_v1dU)&d1d@=H+v;U1R#^2Dk z7+3Md*z4dV^LueLasJumd8TRw`0N?EI+Mm*(at9sOJ)f#C#BjLPtEV$Xo+AOz z4$AAfe0A!Hu2Zprw6(>9NGWEctT`8TW>oYL`@jAEKsV18d)9WL@RM3NMSTWwfkH*~ z<8A#bEIuvGxwMRHW(R$#Iurdwj~(DLKX!UFuwJxmH+n*+vo4nXPqMJjBaI8L|?3I#y?2T5uVr`J9(6~C>^9*qd3(&>{ejU z>eJfVIi3+9XpV>bWK6-(-=i5lmMOv!jt$5Q^;8-6new)mRyoJDA%0pA^IiApGFK+;K_KR>*xj2@TbyKj%&?@ z(Lqmju^$bkr)=0YE~n3MP>_v(_{^bzo?piSv1Q`Hzl_R8M!v#8g;FyKL=E{tOmyFr zp|0YkB^Un$%1%BLwS<;yjF}^dd?q#Z*Al4+aeEjuT^8B71U#h(eUBa$H--k<&(W3O zq@GdMA|PxEr}UxtU`hRTv4_s!=t`tlu8>&^qz))v+S1u@ROQcsNGMPs>aSl#dE*uf z=RScc+S)=7e~LLWx-H=8kRuKXa!PHl#wrhYaTNt2~sdfkkc7 z)=u$iYo`x>g%Rr`ho=Jraye(TaMc`Ex&1_>Y#iQQYq|bz2PXv0eDoQc*>HdP+Ty`= z{^CJeq6JiBnU0CiotUMJ7VtX)67voV_?mrieeVcHM+lJ7Av8xW7Ye-taW3_8XhQAp zH$%NeVSl$GtRr&rzp^>(KU8N-DY9MO<^C#SJ&~}NIZ6ORP-QL8Jr?y=xoP&`<6c2A4*#xmuFQr2U6ee>+iuZ1e&+d z?ZL2r?16)hsw_UDwC6D3g8}1x>!9*csLEM*jG&c=bDgSXetG}b%B?@C!VLhNGeCaN zhwx-1tBCNIB`zEZM<~KU)(O9YG6fLGib+o3T|!4cFe03~JGYXHOz$cy&CHZj3DfI_ zv40XGl_5-logjRCx=_TTtGn${Wn+WlEK5{>8JGV4>YiryWa^CKVdw*j9`-4Eh`wvx!w9(ZyK*}iI**!1LtM@P9@5sf zBh==*qcD$a2g9kCj8lK6;!J-uoo$F~9z6xV_HM+&h zS11iILsK(}LYoG@YcP^rMzYQNw?q$BHgCvj5IYHC2Qi`>CGy&w3Tlwoh&xC z2nLvZyQ1%?KC=^LR~30ls&U6xA+hl|43{{VhXOqhW2x-iKKaYgDsnTyxC@m7E>Q&#S!d_Y_U%Ep^=A0sXZ z-81S7m!CcZqtI5J;UhMX#ubzg;C1j6zgd1qmWhiZ?^Rm76oZJqUSzMnfgL;!W9IWW zaY)B|#mO}Tdeuvzo$pYMlXfFc@8`i=v{mcL-cO&XH84+bS)er@C3}sy{Vw7~TU!lX z^5c)>2X8irWUMZ#zvcyXmONFBrrBClXWVcsJ6?>Q%tgkUTm7PP-&EEq8N#muP__Ak z^Fp0b4JDuTS@>7G#I{pUHg0&HodX+04`E*AJg5phQ5XA{q8y0UUY*S@vc~Zxn7RRS zoIQ%zf}Gofo#MpS;ZfRnr_#nndUUwjIE)8oiq58^V{Bo)vB_fYmdtP zM&hZl)BHCKmWgIHyjsl>Ypw|HwZxHAE#6CuvIb~Vjs8uKy=FK1H`VAy8hy=ebmPEA zUyp9Jd6jW{VQ3+V6*-!C<-6Da$36Y8@VXEdlg|D-r04rLra3X$7bViKD;B z7(yr`_{*T(Q8u2xca@FPH{kID(Fo~SZL$?}ZB9ePU%4F&5~CBIUXjT|2o*ytf%9Y` z@98jD1n@4?Fl`mZ_9Dkm9*gd>Iw?*Br{R;2dvC{9l;&q<4gTYawem)UKk(;@U_^4h z2)an6BTr_e?)m}wYs)v}?-f!+jwo?U z?qvJ^4dIPw>V7njsjOS-mJjV)1`AX|8Cthv_Smry{s*DlA0I4x{`7vdf9;i-LkaaH zyiI2VUDS^r!gj=;0GR>pl_^7QF}+`XIVE3S67<`-5;DC&!8yXM2XYs-$c@@lQ1|MK zM6=Km5i;AoYe#a6N&)nYk-k9`iwr?(`5aE&Oz|UXQj0W>PFBS$Q6ucWUnvuAV1{d@ zH%AZ&xiqbM6a9)5mvTp~O39wY7&Rz4<(xJZy+A(kj(()Y#n6^MFoxE^XGF(7G)DJ# zDju?4v>6dt9K8*u_#bO!+Q4?|jM-}FhxIKxyGu$xnnFg_6tQ|yEk31sj>AO{W`1MO zH>aTqE`OJK${JkqzCqqF68E4Je64}sYCto)`uVB4>spYef`Q0+Zx%DblxW}hmW^i@ zL!I0_YJQPd(XYRPK)I`I7PC8A9hA$TZYs=N*k7N6_*~k5f##GNP#w6+D<-`Rmw&{<{_ zGwPJoqhFHHCS)cWjnRYjIxA3znWGdvb&{Da$(}m)#w+Zzbf^a+<7Swr#kBu4>_E;9 z7tzFt9TGummeHmR95%MK#9IOj5*oD0F1BlY<0#`p;0b7=NcbQC0brmS`Z2RITrFs!s3V`*kufY6%ou!|% z1NlTW$?PSw1QyIJmw15z*#&ZdS6Ar`jBg<4MM^pJPVbMmRYmiY)n)$b^7>H?AC+=- zVQn>Ao98ta=8}w(V(>GjZZycQkNIpi%w>1uoD42}XPq{y3Zu@CQ}NS{F5>vfjW@4N zGmeBAyy};+SpT5N|E`v52VT)qUg4RXQivNKrE*h(GtpRT z2K5*>7cTpVo>sXlME%ZIpLu*rf!CN)s3jhv9vb3Z&VM=O|9Waca{O;=spUMA)rJ1{B@+^T?bWKgsaxP!f8qm^{Cv(E5NuC- zc{=^sWmeH-(0?YH$$ULKKYiWDs{ymRFoL@)WuqMeX*I$6;?zMc#xA?Rg9P4cr@yo9 z{+80;R^x4T`>rDo?b7}5XUn%Tk0_6pWP0$4sxFLvpc>Q?((lY~0iPE7GH9|@1N1@L z-im?@&%w1PXRkH+qM%;=s7q zGbs9TC^d_i$y>~SrUB5)2~;VrpJ zn44U47s|(xgz$X;?s<}7XKL|!7`;pR!iR&Psfn)0h}05ZEhpAo?uvg z8^fuZ-u9YaHE5_!Owyg^r5hBPPX*_Y!yu2G59_Dz3fV*!`VLc@agf2(@AwvhdXBv? zo?6V2e1=}#bQ`|x*H7W3nYJdUMRO8pmb+?G(@us{(|T>QCvlKf`$gK?n@-jlH#ph4qC{G^V>2iqthFHrJ1lUEEvvB{?>eBQpF1Q54WSwfJo$ z^EvSlPj+XHZ)6kBM`h1cX^97TCTDZJpq}I7`Ik$9Yi0Xai(klJc8*v55*Wg%+R~S9 z=1w6SKiZlh>XODdx9Ql?vpQtzZDJFSh%3kdiVA#@w^%Ba=0x5wj+{7T97%U`$~5Jj zh3J1t;|< zzZE2y)zk#)SV67IrzW9}CZ;6O$F8tc{1V$9{@=i#XP36Q-Rz=eY}%**6zprsuGa5l zTT)B>9N>;`9GqlG63_7t!a;YI1vDd{&tf4mFe}I>Ij#~Qe?bnbk(4a_3QJo2YSIEI zr0WIK1PjtZ*ogxHUkjVuTKodO;@iFID?E)3Unim`Gr}GZaru^s2`HFSjslKl(d}&g z;EfwWK9LG=1!w{Y&k>fC+3y0H!k>6%!b_u{Sbrz;S>MMB==-rCSg%vHoVA>N7XMV` zTls!g=dJZ*ZUQbLYU@k3mwA+W67n4t; zSgwP31yqO*5>{cVc8dbZhCf;)N|wCgii|#@bQXClHMe7AQsE}q>clK6%5h{iTj4=< zF?5TW3wz1n_wUG;U!#njg&*8Q%}*{=A)>_eDoY^bbNl2XwP0K)e^pQ|yGlesi#Y}= zm11iL6jP=8<%fy1cYn9rSa(cjXi`fpH~oZiMQ zy#*QES^p&w48&ea%^AY8Th?F9owlmJ3Q^2I#ZL*bF~lP#pYwq%Ibcgd?t3+ z-)@8GYb7||orU#bP$ujkGMHUN5-6-Se#U4CD!qiB89P92s=qX=5d8`#jR9YleH2LE znJ>tU+#5`N+tW7Ws*wMk1sFp!C^VdnIA-2Y3DTkm2NbLSr)-5Y&#jP~zMoM-PXCq! zkZu5`959!ww^ez@S$|RV@5=Ej>^8?{9)a5^t^>O+Pr;AtnUjtfrpRTM-;vJMLJl4N$J)rk#(_19I z+yN9i9p55zoUpkYSQZe8ZWnyZ=!oqt9WL|`+g!t%-rT@SF5syZslZh27bDg`!w>43 zl6n91b62&ix5f7{&ry%E{Eg|E^Z$Xr9mCu2T>e%$ zfWL`;D9q?eT;oJfR6MLH%fH0nl`)zIaIf!(ml6efiu()L{CM%%{J0!!YVlnD3e+r; z!ew{7;6Oc3c=0^pz*X`#U*3Ykl@)(3H77Yx6J*Tg%EaRU77jCA+mY(T|xGaQp!reH%^v14qBu>D*!`zKS#y z4QCs<)@{U=dIKsy526?B?B;~8pwq5E9U)0Av^Q;&UEYRgNxueH?^xU3_P zy1ydU{Ea{Xdv_=FhBJO$o6;WYvZ$c3x-a@R3jvN0N^wyUetp*~uIzHVDsXsen? z%nFP_aI76F-o!cb;tsyBs5qZ`A|u-dAun17$P00a@^$O}joRwx09v}@FgPuKiu$p_ z3FAmF-uZL+6ATb-6p``=&i)d3>mTX>z4_TWJ> zbK=ie1!@Q2qu~U7~%m7g!cQN(fCxGrPD&~m;#ty~nJzduKh4H%dgh@(Z z4j6TXT3qyh06xAkUyH9%y*bFa>kqV5YxyB~G-R_vtUR)KSbox8J;>hWWGW#fV~J77eSCG;!6XbDjcF*g9la^4)ITT$MD zV(|@PiBN4so}wD^)Bltq&o6TU%tN&%}kURYoF+jSV_*zTT^ zYW?$Vz8~5bPQ)y0ML4Sn0Am_|bB9K+6zMsnM6T4n@h@0X;UL z<-Y_0S>Knk&iblu`A1^f8x83OV4ijOhkjESZw35xgtqcSvLc!m4|nsUn*He13$hEc z9$hFWxXnZ!r}yRtyY|Ud(T(c7lBf9vjgRgZlbZ9za9c$bWK8<9Ke~Ls%nv=buQc;j zZW?2mJ6Nr@ih^m=%Zuk5<;mTdFmgGwJ`6|1W`zvwYUMS5>-$`#oHM4A6|43 z?qkvO1Ib$4#(JRN`jP$k52tQKr9tcJ_ADHk!j6PT&{p}ydlcUi@bpB;Z!H=M5Jap? zY+n3xX*1SRky)+QGlC0gHt}P4i=ap$W`!)GvfWY(5^dE1a&QFzXC%w2#s^@;LDbr| zoC^I#J}{rQYpX3@*cNTj5|7af*{bECCGH~wVKAx)K_GRwYqlG4f zNvJNw^7kbKPi~}Ci5ay*t)OP}Qv18J%oQV-|FD=J$1kd|bXpRBlIl{~phP5&kBsVH zs$1q{%EAx*hl#vF)?n-~da4%d8L!1d{EqdEi(sDGWv)Mfn88;jPJ?8V{JjbpvkUt> zy6OT5ndpO}uxqcp7xbB_!dwt=W120O6V0Me!TfDCFBLAzL(SCFr^cMH92IY8c#<+={#H6D_#2^V4o$; zW%Sfg#D&jf`vdOPQoalKW9vaRvz--mniNJlYeV>Gz|hPvf6Z6X3zy%JFTD$DuWS`; zh1uYAv?s@jRu-&p9i;LfQ+bnP39&wQ^l^$efL3cf+N7et)aYAYQ8T!5v)RWGon6_> zvZ1iJcs;>*$R1!~yd_T0Ow!|7T3xx6x~w371SM)sE)=F#{fA6GJu0e+koX+QC@cB? z`6D??0my8Xod3)>j0n~xWE6xmh1p}_Qa92q4}Y3m^}@M_>=}y6*>6#D@PE;bt+shg zxWWDU@{e-nQS}iWF~sL39O6eUxKnQ4Bm{~a^j~b>Wtdu8gkgUPG$n!IE4Y+5Ue-X> zl2C_(ek1SP=0mDbC2)uzw;Y#|e{uyz6Hk&JR|#P0d3Ac76xk1xTBMzFXq`ptEWN0w;pA=F%6p+n z;wb&@rJ(@0JycuV6!x^J%MP9S!!Fi*PIgE;I89626y+HFa-8J_Ts-dtiT##&?p1-j za}h+`+w?7}4KwqseueeUA0S5r$0|;5&q5y1=R#o7k`?R=+j>VgU#{0TjLg_Dg zIiWSAatonTk&VmCeE%){-*SNzWJ)K zT8S@e)1S~oj5fUepyAaDu zf-vi6^2+IBZAFMOYO<<1N9fJzWkXl4!@D_W3%6>O};ms{e74! zn@|1lZ>o|k-&a|x(7FyqGmo`14E<9sFymrXpkoI0F70v+Nq0 zglUxqlQZ(C89mO%(3?NTeBCVJj=mP=*xiKs;j%k>rz>g@Ng{n*VD?~pclrZuzHqrz zykIXVy%SibaY!M)PY{26Q=S&DrjJq%pCh1kIMpwypkZ0=*KE4PNi+9_uTzO+P`uC1 z#=-*g(Ii5jIiX4!pzl!~ebf@S(*grzhdiJl%|=G+$wA2d#tX@m@;0OhB&QRaea&Q# zb(I3Sih27r_+Z}dHRvD+Wh}tQ*|c_WqO(7X_kkOD6;H2g1>zTVKo$a|!m!Hjk5JDR8K{gho7KhULC>6;ll{D zStS&4}+xm??v~u?8r|y)PTWzpL`A(0*7i%%Afj|Exy8?Ap6Urt>nnhbx(PcZbi57s3F(Qb z1=*f_NXh`2+*oAoV_7U*+0G*sS&URrdzp&tTArf}q5W-h+&pUwp9fS?2Qa^&4zN1N zk-)L`LC)xX0%Ny7$cQj{?uR$+5ANt8_#qN^NY?RjfUkPM%h3~0O?-2pF@%UnM|fhq?1|v`SG#^C z7?DBAP;b{hBZwnR0z8b0ve~qSA%1@IA?drIG`Nc`sbUR zFw~Pb^w!|>LuiVi_NOgDPZ|IvEPMxx?b2z!4nj%Si2HNeD%EjE3}cjPuSs6a6>7Vq zUk_n??G5@jEhw*n-4WPgdt_ARVpskJlKF(Xh!%1V$t!UCH$8YzH=@Oyc@{61cUfg% zKKP*yR&iP4W)lF$qUKF{Sf3DTHSG=i*zAFQv~WOCY#}kuH2}b_U@g&}=OkEjW0Pu* zjtizHD=@|oT?6&JP>u&yopZG z1p;HS>fgBRxVfiH*kQX40{ggrtwcp$2^%fbQpGFO>74+2vbc0ZyDeV)`HZQ(3qA?^ zxT=gmffCF+l$z7jn<>Im)C&&S5Ygfj;giXsfm9Vc#*vS>DT15wiqm`YstJy@k~sU# z1jo{SohsBAURLJrOJxKQNV3u)EWen*%e91;YV_pw#j@;^^@W74skof(!PzT>qchVd z$TKy(?k__f`*gclD{{;y&@`GP>grScVtpb=JZpXOIh9c<}Fjtxx;^GKRfD+r9rA7OvSBvcRQ)XSLq@BI3Z7ts#WI7x9ObG zBB$fafh@g`1Gi=O3u-+ICIG@M`~0wT|Jb6h6KF9mMu1vkwBt|=d^kb!*Sn26H)$&t zgV%tuS!$sY@fAQhE+>*7DKjJm!{Hm)b7(!NHTr3t5ToVdi8YFM52<{KkohiE{Kc|T zfI*GOrC$8q$kw_|vOkgik zMtiP=<(AK)1}qk%7$&&L7eH{8IyA$!btrA5)xD{{4t5vvwLn_(a8;`o?*>Q#YZwV6 z&eam)j{}PCM_LQ9$v@EjbXfy0u>Mi}$os#4lK61rH)<>UFozO%XQK=#!!max|MMP` z&yZw`{DBA$lp4*;59H)j4HB{o7IaF4p!l^kB!N<))bfo4QI~iLso4^zUB3PWWH9!P z7!PQ1_&n9(Va{^^C<$Uv1#V*4-&sh=&v6|Czzq_l5<=X@CVFE&Wc)h+TL719N5yPU z>8p1BQ3berlP{fo*`lpnM@K-UbeRm7G?RhB3HWz7!?C2&5`SD^9Z={ROubH_vodQb zF>x{x@GxkR=s+@cUIol%;KIeFqH!`2foz}_J_`WEsHQWiA0jBQhI5-^K6Er!G$kgx z^ts$zRSH6&C*e#3Sk;oU32j4ijB=@0 zc8hYu{&9OS)JWgS%&gOH$-BT#Ni(D2VZtN#19p}4NUYM0vfU#bAz0f!aZ0ha`nUWh5KPeq zMR&z5PN&$25G&VQdlpw9I#!2y7ZLJ%@WSwp{>`(Tu4cZ%Maqv`6%E5g~f* zmuY{u_RE&|HZA@LJ##o0hkTYupO=)~McWm+(F=!RBhBBwbfnQTmKd4z%`>Mg0*$I1 zvMlNV_YzQYhEul+!x$jPTn|i{|3PyPy%BO_Ynzavhw3k9`r~0^bktv@`(KyfiaxT! zyB*=}+XRhB_wQnB@(!N!X(;|Q#vIx7xDJ^%IAukSw%2YxObAT6`zlj@tmbH5Ot0VupHVtkG%<^Yqct@<^;6l+!C{6_E#$9rl}3r+L+ ziyeHRHg7b)ewiZURVE-aW{Qza;3TzmGxISy#S3Hen_6l-s};z0hKD~vkGe8d@bVH! zkXaD_BHyiH{OQ-fU3j4K`Q*wW{+;4QhXLh|viXW2M5a{`uI);u{H=M{{7z=wxDGbj z;S55S<_QVw1nIcqu0mm1WZis6)@ccgnZ1stg3Be!$Xeb2&p+I-bX*U%i7OsPiab|2PGYE8-~&PldJ;W{X|;< zBB0#(j)w-bvBFVNcpQM-Irz4YXibCfYE90#*?b?{5Gk z?&(#HKM*Tg#YBgR#BYUJC|c`TFK6z|AnnU<|6zz)CDA)A9+z+9_{Kts&(4Q-^FoqB zT$tzdRj#9^k3AAe;AQN;7;*T;+AlS~!d$AR_1-WJOs!@!KLW`hyP(asGnEQLcv3M? z5pzO9Gt8+{1bAlpA~guz#)BdQb0Uq&+_t$_Vxcfrn>_In80AhE zAysYlwL**iw0rL83mH{pE~0)HedfCeau;#5Vgn?DWX7<;f-xDaz(kwjEZf8_E&x&` zbGHRXqGE7byy&rIgfm)oB_qs*frz4T*@R57^q13dTKpiyqpg|2P_#9i^mEXxFY+-u zj?AJ`aM8$4@?LY_1z=Tw9u7Wf0_=MNL(_R5Y_u%eOBwYQRZ2Z`g`WZEqQa3P|a35*+^V*y$;c@#LT@!si+a*B+ zbwN}pa?-Vl5QhjDhDvcfvj!?qOpHOYR?%_hRK#hV@qrV2aUk zug^d7H>z;CpLLpo*%rH?jBJY+iz$nf_Tn@B2yBOo&X89kN?hij1%0u2wSlk+@ci=Z z5TmzP;u?0u>i3uK&bVk&0G~X5^nvBaBF*- z9FwUdJTZEMRVOVl-xU9|WVYEnka!dv_B-tIf}j>aA#5fxxbiP#o}#Bf3+6j)*Dy_| z8K1$^WE%Y(N`W){A$Ho&f=I?{Q+5=p8pnqW;u5uS2&L+Lr@T?}`Btv6E% zp(?f2vaKE+TGj9&=On(syXt|Et4P0P`PRP)_{{2hJbq+ae`-~dOUWx|5QVNem_8QP zW0eb8-w{Ygzxty%^GF)W@B(aHaW#YqTt*;6F^^=;z07gTv zjAFAridR#Jw@w)y?U6nF(h|G)D`Dj1EQJVZ;o}oBg&}`)p^Q5tKki z5wc<+vy3TU4Zfmn>$fNsI^HQ^0}d@&+qunUYG39xFs(+}9dc|=GzZ!pT+5(>Z?{rQ zL!rqkTJh`DY<*HG9HM!xN~vBIUGm^lLvvmc1x3jA`}~0H-{C)GTHZ>xhku7OSuAgn zKbR)}Va05{3l9f~v!IiapTH_jp~8~mCDMy5a+FIY{xaXlQpVKt2U5l)MnouM)!ov^ z)Beb;fbkh!t14y4Vw)<@A`or$Bgj6IN^;f-!N?_k%3@$WDCx=TOGG6VJGmf-3^~?c z_|P`k-%qCN{Uj@aV##?D1-6m42N(Rvq-0ow8CTAFY`rW^>5qCXf?r zNHhglE(bjA$r?!j&#GrAWOr(ADX(iP|o&PaX=u;GK(G&ks5KHvF0fN}N z3Bp%s&=P~FidI%XNe8mMt|d{?w|Mu%?)A7IhR2@OjtayZ1pw3oY0+iS=qvOSn9w>Ty4MH$3CJE(HIt4|W#j|6q^5jh`cr7JQx`HgT{LiLbbFJt#Qo=u5+aq01!PCn1C)27e>gqt{K6C zA3%IZ(tBjPYFtCl3@t7@`VBqz10yfY@{sX$Eg=qOoLnbbfNuxOl34eU`e9Amx?w@Q zVh-O%0}abO5O7;KwYWr!9|yY%=mGZ|u@BI&1Q#xNm?wHH$V~&WS7Z=8Z?pNTJZ+jY zUqVSO)#4us!X?yQvzJ|}Jb-_XJ65{(=mjm5TW|qaKXU~7&c17gL^*hh587KKKWMQG zZ6OR~jWLolk?f6pt!$dFC&a;m8e@7ffmQOjM^N`4)mHxq6uhFy;n7QZh1HJJCkD#6 zvPm5MWL@h;IF6I+4MnaRt-db*d)n$8u%+{%Q2ApV0eI!g@RoXkcR$OED1j}Y!~Be8 z=!O|IVuyLo*bA5#p& zz?>Q9(&zzdq${AiT|HnIV+Pzn+_~jzG~AuEAkk z&Wcfd;j7%K(0{pAzyq+dq0~S{Ii@&FS@$!i9eckJR5(@Lhfk;$XjZf~SWCPKF^5~4|T-o7WD$wze#Up-^^&`LQHN7WSPV6+555g336(5Z|jZ> zx7&kSw{=H*%rZ=`Lf1r-TU=a!u`mDyUpuixMTm(;=I>f|L^s~|zh8g8zOIzEL$zF~ zoAw>myEvvqMnxk*3{S#)Ho#+Zj3L&=fZjhp?GbXvi)$2#xjLNsv;2lR82_7M6(g)g zpoQU*>oYBJUc!EnWO2YwDkqfaBoJtV5Z0XUBp)3%Y4WG-ty< zD+tYbEI^&u>OZ8s5bJ2rq1Le*eoY?d<19YvMu+t;JdIDu7Vy1_%cF%A*>{JW3Rd%6 z2^)|br=YBZ`>Yk@7sfIs&`+t>(`XUF)LbG?SbspA2MoM2j`}{Oz7PVN4TAQ_wW%8j z=}cd96bTa)scVngQ>#gMS4PGNJzNQQx3T(6+_~$962juJ!_XMAaZO+vb#VfhMw}wUjQQv#!6#%J5 zX9F){wi##L3{4PZq6?KlVPLEsMH#s!tpl4pgjQyfky=8v^~L6mjc5j^2=>50RKai; z0MEq5#Y--XeKaO=wOpsOu2&JR?}%Kf)3Wq31(^W*G+9~kjPnd&d!I6H8!n?A>o9$k zEf;B&jY%!BhfnIR7P+t0PGvNa>B%Mf!TK2@eLDQdz&eJOY0~+O#3J6hB2|-3o5-j#ct(a5g=9$g1K48>6sF ztCLn0@mO+K_A7#3Yd;lN9dF0UY&2>5uy4jb1{8@&W_kGoPfvs?#u{~Gaph>|$X=xWlFIYy5-4dD0S z?b^e&bp^y8tHysI*lXuq^iI&n`IG8X92)J#IUtv<>CFLy8_5|fcJn)ND#*>;=<>@c zYP+R<3hOa@m$)_P&)_t?@Iy8KrA%^0ukuas6A5Tm@1ldj6K@7nflCMC}g@#XDgH+!`BhDWOys zee!9mM0odf5XFcKt?|vh;&C*=)aX&w!*f*F#dQ@x>hddMf#}+rDi3l&JVCgz_Yg%6 zJvKDw1-N9+y6^Dk^YgT1^XUI`eR8dK_TM@BYcQO=`UTcSL5wlNee{NU(N{$vBnbsd z(00TbyM^$GNiK(C#G8Vt+1?;N)morS6mS-^5B0WB?xlvDjEoaveTD~le!vquIcDJ} zqU^^1jY=h11x6AZae|%3Ga=&?Y#BcIE6Ub2$+@pJg(J{U6XNY0J6Wo&97Dkxf3LRc zx1@wnz2-<%v1QX=!lVixJ5PV9l$U_-b+x}U&Dfd#Fjw~-QsZBfoXL)jB}z@e=!D=b zqT3lWdjrPyt^;BJR>JTu+AZs!@@bIiO68h>%AOU2UDeyG)qvW%S16W2nH?0{tFbUDV9#fVc&v$i zH*lR7K4y4B#;4#77l<7VCrcL%cjzUQzQ<1bqW3d4oP|bU z3MJDhA*?@ws>ys+P<@9NNJV7|_U(~c`s-KtnP6;1$=oI=KU1)563p~BlPFWWHJJ2n zVHFv9D0b~WQgXvdCXjba^d`j#Xyjc|`$qpzT9i4SnPePQN?F%_RDQXpsxWbdU_r&V zWF4Ioyew@tqF_p`m&C4pRBAQu&o|~4M*e_$OAc;4D3S;8vWjYTD&+2!=ei4Wy7tp( z{#S?R;A9bEXA~DJ-)3<9Q)z|OZ!q11f5w49-1qC*7F^yV8~-@bf>bhf29448QAuWH zfBzLe*|a5~Ph?!|NBITIt^yTpLXfqyIrNtI^NEbiwIA>|H<)Aqjf?p|EOLR3bLbJ& zTjwhe648eNZ!>uvWM6($ScZ83zLmU>9V|~iP)zLXa4J+1s@*|1)Rl6SN7pcL@@v7O zfZ;$l_yq$ArZc7ft;_ZW6F3#0j*OnTsBr1!IM|^q@!Pm#Wr?wf`b?UIWD&7hewMsG zACE&*G$T+>?CS{8u(5QWYvz2X5ZGml>9A>$H}fk;J}dkfuc|3xz9DE~;D#D=OPsZc zjfQ@Fo)tf6QttbHq(8uzaPp#Sg!tz9N8I8J+m{?`|Ah`+TY6x&!bJvHtR zH>NIrKJ3pdxZTw+P=z?kr`#T8hn=VwFwlN@MNQICU;@cXsV}7cX?xgnEbQS@oNb6P zajAr7FXALJm)5l7X-wv*h7TNxYZ)x!=)^ zR3mY>?OEF2et-M~90qY<>>|Jh{F?5iPr=|n!^?YCFnR4~Uxub+Gis}J9OHWG@<;r` zb28etQ<1L(li$FtJ2HW@OvhV2k%P*Q-!kPGjT-Z|$bI;3QpyxAyzNw!lWjsO5sPbWa|(f`DP-#5A`L`%P-i_oWd0;EQ_Ju*~wM1xNxiyt<9_GqEZ zb9o^%t@6;>)hlp%OgX)4gTrm{kzK)d8N-~+<96$FRO{CcxBD3x6T9{Sfj~=WYVHny zl3#tJ2Lp+9nts`ZzA0-?q3ZU?rQA*<0h3&^H1b-;gEFU)=Ys8df8@9PYXy{p_q{jJMRm@NXaoTT5V#T%&? z4-i?U)nCeqX=HYw!7H3r(U+r8VCE+tInlG^YLVE-W2U7pULKj}F06cnKdEB=j}-mA z8T~!&WqpbMzD+lR=8UAQ)29;h*jw3>H0SI*Xi&c`yPi zL;C*X@wIA+^O&;)3dbwjZX}fQ z!d}BPPyk-En0zO8E6T0uO^&1qOM6&0G-ZG078XyST8oQ0FIamED~20OjJa&m+{waW zMu{?oBZ(*dSnhCPwe(nq=%)>27MTE(1$1$vMvnrGYOUfshMz8`c;hWuvxM-Y=KL<6 z)9)^NJ?wvD;kB%#6utLjua&itTVnQr5^td2`rcsAhy~TY-f5{hqiF4bmq4&1;r#Nd zCr2?z_+1|}<_bH=vS{Yh1_4qt&KD{-x|Qr?8Z(!yK_t~RA&hQi!<61fHual-q)@h^ zv*e5LYq+}(jon(Jv^u<%V@5{}Ue}@2ZM`+xOITcwjA6jB!VWcZm+3EH8^So)QMsK1 z-(4oNBrb}tyLCuenc9ne$3(R$ZjykMSjD4U3jk?xxZ#XneAw3O@q(?yf!AFPRrrU77!K#@{(lp!kLC zOQC+hO?V8`D;vE+QYk?tQTxPq-q_4_u)6JOY;2LJ-GoKXDazqkKe`LSz&l{a-^U2@ zX+V3i3wvr;Mp@~8OC1W=-2K8e@j88xiMyMgAt}}JI(V^8EDjDL;A@_S76L}FI8vjh z9v}5ObJn;(UCAEsZ8x|_K`~}0-c;pgXVke8=f%xbNYT{HQi_+@>xSqC+=`Xid-;K{ zc%h}2<}?)tj0=<-`L=+;+Ie7oz~h8#jSH$-e-V~;GV9_-xJZY6T!IxMz7euo-riUg zJ;Z{$e~Ei8zf1-5jW^f(SMBHgRXavMb>92Lfo&&!Tjb#3s%F|y8noO%0=?V(vjzV7 zFdOr+(*^yigtlrKnS-f23j(R{9-p4N?@L75ea|)sy*$O0Q z<24Nw!_%HCxIJC|!}VkFl=gA&Lu8^8Z!n;}Tzz)&g%oEqf#L%>rEoI0zL#+S1C~3^ zFUUNLNq0KLrr@{fX@=&*`v8?j0^lHb-!S7WcjU(;Xi4ad3o^@(m_HEn()Gn-tBWNq zjhp`dJ+4j65lgdTP(>KITF)yb5o!gI)zxlkuiQ6M$=<#rol>6lRs7#qEy&}UiIoi+dl`f*RHE|v0QC+SJHWc=RCm_DrkYd}FGxv3@JIwaDfg#4VGg zv&+PE(V}{)b4S&4+530w&MQ-sT2Tfiwnaze$_9Ti=W!O~9invi{)&5=c<&h{TC|)Sv-ez%>z+4he7@IvUvq zmr)c&R8)j+!3_clLz=b~6?bREbr2nQS;Qp^BrKx>ZUkJwWvsSwBswBNvA^H%RNdRR zvmi5%@Be%M&-dkdp!(K&>YP)js!p9cWx_K|*vo_mo3M)scNLlTO}NQ~YfSi>2^X30 zK@-k3;Y}vI%7o{eaHt8-Fkvqf9&ExcCfwyU?VE6u3D=nLH4`o};e#feYr>mMc$Epy zH{nndo?*gXCOp`LT}-%ZfN9@^n@qUIgs+)!kqI9(;an5mWWuXVc)kgTn(z!0_A=qY zChTCs@1fNo-+gYv4JNEL;Y%ib%!GHD@Fo*pWx}B*Ji~-XnXsD)JDBi$s41j932pr? z_Wg~=bxncz(eo81DQix8J)>kyh11vYx3m8Wt(ud*R2Sg1p!Fqi)*I}J9aCj1_13&Fn$H<+;2gfE%!F%#Zp!dWJqY{E-TINXE-O?Z+C zk1}C56Lv7+_h*>)O}N2?wI+PYgpZldLSb8 zel{W5H33LpH3aUtLF6*<54LpLSpruI(G!qhQ%Z-?Lz1JS`y_wk2tFgRdJ_Nb1>or( zw|0Tc6zCJXg^P?wnsc+JAAm**>O}jx-BVKpM^Q7r<)Y$5b!u6 z#JJrGL1q6DmMO6;o1dsZ<&uhCY{sU&8OMPLURpRiM~%RTg+nXoTcO(#4IDgLTe$m* zTgzM^hjytGnFj}=xLNzB#Qp>0NCj7Arl_)aFlb=MT(yQ_qgsb&Ns_U}{?}wgzwYRu zu~mPV-q3!!UVq^{8J?3gQI-(WS641 zG3Gn{c8$$GTuWiE#kivp$0CO$1Kqm42hVicd-t{$?d`|*U_sr<-EOVA!nFGq;G@RS z-R?7pW4n(J5@cz8Bn6f)L-F(;u!$gwzSMz86a4)V@dmN2;oH^Uf$yBUPSDGySIy#u z%miAn*Z@UK@^aNhsMCd?w{&~zk8R+m*x=`9I4*P%wRQV9CE9KxBX93HNHM% zXw#`yoo?WGw+(zPH~5;O;Yh;Q04~C9e9c0g24BY_zFmB+>F3}pJ{PnIr%#yK9==X+ z!?{b{q1#hK9&1h4lMzR{t}S-jCJ3$ZH8j!w4BfseZ?FC5QZ&AvVwk{JX9LGukG6*6 z8N?BeVhu+UzG(UB;A=SQH29+Xfp+or;293S-VF)9tajo3HU35d&Ryza-JbfW4LIuz zoDbnWLeqBJ_&PYz{yDn+RhWO<=I`ljALH=`HypL#yzl&)gs&3~oL95~=LiGm)kRL*ZR6{k0;l~W zbo&dZw}-E@**@?!*bPUm`kU^z`fNdK{#uJT^4Aw$fg=fD&r@?{cHbM&8Xu2P>15;M^Isf%%t3s+_~?~@Z^j!UPp4t=&^Eu{jyhfV z`A)Z|mSl8pg`dX}M>ulMzRJ z{d}g=HbH1D&u>b!|1KUE!+es{Zu_6^()jw2VM3mtZs2&US8F()K^)a^!STzN3~()I&# zK>SYNR4bf`pMfJ>`-$**xmc5xsV`8!9M3kwy^J^(hnhdS8=k$mbHG8iu8{A);JFo8 z<>r?+`*#eEF6}rl#}^)sLk>U8DQGbyN9?ji zNVJba-B7@J4-Q8mv6WcVAA*DrOl`4O68Zb51-?>m0jgwv@X>!G*3j^5+W>d{`glLw zAzqe+heW#ShM5I=M7&m}HP8MCzht2uFUv(f+)jb1hZW-4xGZtehP@x5-V2B9+>hW9 zp+LI&;&kF3&Z<0CWGbwZVIf!!crEy7_bl#&ok(i`cvW#e%$g93BU__C)*8yi198k% zU9n|`_1>0>n$Et=8d&X5`@)wB2OGn1#5550QFwh5g{nsMq;3t?hF%nb*I5+#T#BqW zMQTt4Bs8Zop2au={{|xLWi;!L4AWZ&_?TD2c|e6TPB(C~$8oFQP%OrAu{o~(TH*6@ zd_S$HpUkm>S#%qonEa`0`L1As9^8)sxZ6Ab$RM9b8%1P5|AD z9iV|+8-DIz;q!jWr2e^q;v4$*_XZB@Ul!=wzfZ7;bVau4)2GSi*Vd=#$dQPoei-$p z_0I}+*HzZdvkO%sUCn{}L&Gc{R(R+tbuJL9{mWx0;5O^pz;IYUl{fF}nR5)1{E_VMdeH0UYTvlDxqkZU z!AVzXI1Bpbj8b4GrzF%^v2{{;a~3Si*ZER64659GW5?1k9uD4L;{)fD$c%Szw(Rut zZ6K6$lFa!~splQ&+Lz)M6K_LG z1@b&D-YXXP(xMlDF^JDnI5m!V)5H~+=->Z?`YW0Tc;<9NMHT<_dMY!8)#~tn9ANk| zH$~~TSN)*x&DiwN@+!VwZALtqW%E+(%-@j@yw+Z)Vy}@1q5!}sJRYM^O!yc6inRq(}`AGoG8eB<^~8hgxu(D#Ll)S&Q{A5-@BfsKK09G)||7YUkQ zEf_1)$O#&bz8loxCxPfrj8oDW98L_eZJd5YcJG{P7b>HgpU6A3Qu$;+C)zyU*d%2dmzop*jD|Uw$ z!fJxXfw{Clg8kw<7z*sH6@5R=Fa2<-ywVXk@);JXVnsq6P#;IS43qRNW`d=e0^ zsXFxa@!*VCIsKHvW_}WRzIxghoJS{E9W_?{g=G0Z>c zc#Rd-o)|Rd=VB8kh!kskhxX(dzhNM?0~-L(;@gB*IMvHVV7nS+p$0qE<`l_QEsGxA zm^uo%tkB@?QTi`nXQp`;gD+&DKxY^CcYIx3NMX|SRCbn<8WG6hkBrPht4AOkW)4p! zpPq$Km@%6|t2VT=VoL^?q9h2zlJo}|2^t{5^Jn_BfGs;FZg|kh#YBzUofb@O@`2G3 zU+4o1zAK1E@|5KdeHhZ%P2dv_c6ler#UC&Q#*L~x^aYP8ISkTJxnbHD5cpFMsQBC) z*iVifvAD5*+Gu6c4FWtJa0SF0ihS=J7pC3IpL#AAzGU(4bVHw1-j`W#Rm4*Qm(REw z*LfjkDpeq}lrBZ-Lj!72StnUPZLm7d7q8lTTw>w54%C4KrVlO=@B#&BF)VV*Dbemg zE$26VJ1zFP+>oWtbsw(Hu@*Q8O$OGIQGh3L9X%5%SZ*wVcM!vrzR>r=rNXcD0!7ry zZH}*@;X=_8Xw30k?a%N>+l1jrubWkmf#)FjqH12fk#i z%{~Ob`hJUyp4ru`0@iA%Z|WP-m+&=y9swLHz~e0f0IGUpFIKTVHL9myZETDdaRoDa zEvnkl3HESt)c$M!^fk*?d{ z!|xa};`e;a>1swd#;4!!REUJN$i#KPlvfxws@^Bspa?+reBOP7GoObpVOeBM;!Azn|f| zC%@T${Qe79GYj{ZZf)I-C@g4`nuy-K=l0EhE_~|aa!`YE-;QO;MmYI65z5Bwtk!^!|hlvA>} zZpK?7uuenH4MBx?ZNBCU!;Tna-Z#rvh$~jFOLg81f9L`T_2QjK_J^#Da3^&BGIY2< zKvF*)%RAbEk#cLoik$j~M!Y)J4V1uDmaESNJdx}`CA-sy9Ci>;<;WX<#~drHtJn4r zXu}`s`XhLXZ3G`SD`jWB3;Si^H)#@=bh*a-<4AU{9H3%3f<-jy3>9U_+tt2QY#DQ6 zFH>ls@xV!H({UI_&U}LTEL4M~d?hN4{)Eb45rWSkK9Lapiq-FQ@svZKj+eUV!>hvB zDP`$pWw`5UEMD<*mgO0^$%>4CAJ@-8G@x@|kO3wxxJfiME4m$}V6hI=Pl3>`vGA%l zD2wOXI`!R*aIiLt=iWR4FV9!tPSp=@(Y~JR@#3oIh6}Nk&MM_WSU9ybl!4z)%<{`g$J|5|MAaX`eMFjjLABe!FP8Zd`HxNE&IPNLT zl7UjO^~{Rq{+>DS6T^}0zP$k z`@=onI;?Ao7_|CBE1|`RbpgJ6)?tgDT3>_<7NPI4?7aG8)N?;xsEG%^D2l&dQIHn+ z!s_`+vPrbPTZ6xY(JqU+v0MYL$Gq3 zu=-^^!Iu5u@b+`KsNiFaJ$J$TY&v8P0Gxi3uV0_QWxjrsGJ>7JNg1&*_?;!cvts_z zh&LDCJLNkk*58V+iQ(>@ftjZ2#fd}ETTdmM9(AFg*{eb6L0S|WcUle4t^I6fy-3p<$M|<5M4GwcrJj& z_~563?(iRQJ%Oq->YWPsl86@^d42}ROtU)jm)%f-Kt@6Z5>>OYe_`w=k8^HnQXe3M zFgzPpdnj`s6y(h1c>xR8a3`y9dtkr%@;lL7YJK^m415*!8Ks5)h5=w0?j}>?Wn=no zz@nzq*YO$vGAhIBJt~Dbi1mZI3k}4}Fl()g9gj9Sd5%YOv5xq~w$Dp^M3o-{p4004 zke{&VuUIiw8`+EPKgf>8ELfHMmfBfpv0-IG%;-g7jkgpMVH{==crPm{&wF>uWiCC#Bdhe4q z(DJ%S6g~%x35~se39KOt@E9Fn6fIS+JW3FU<*R4(j|}w~f7lg%b*!n-^mpdAEUf&s z7vsJ=tcbbu7J3(VS4iHkW@XS-p}49F_FzoimuV?+EOR;GHL8`f*bC9tZGx-jcztaB zj!Ma%?x_p|T8H^3DIX{R8f{A_#7!6j>eS}lShvBW7NTF?rwcCjZXuet+`!4?vb-{I z`u2{@4Nr1gRWt}L=cDwWljk1-+>EVT!0t7x&0NB_9g&R3s4f=*(^o*!dhb=(?v9MH*pi`{X!3B$O@ z7NU=KS(vvlZa=gsaU3K2V@9ygcgmM70MXD0`29oZ$OB!=&s^{Wq)i|V3iqkjxC5;) zRaO0rL4Z-96B*ONUP{KKXB;;@&*HLGqCheyZ~yijyK#p4rsFQ9EajE7?!%E8_e`)nRJ1EqM&nl3J*Dx0ym zI!q|2hoYO+(omG1GrCZpg)Flkg1|%tHz6cPO_s1(T_s_Yx>UkObv{CNWH`Sg>C0EL ztDV4M#D&daaupo4YB6&xvue@fh_V9`al(xhNAU-pe2#dFx#Ns@`^X(F16!v^my} zBOMkU=0MKF(h zlWNU&E~~kB;bP$g*`PP6Ki@Blg*&*2k7Q4n%(2<5s-J+;W_7Svefn%vo*Fw+mMe8? z401bSa~R}5iTvK7fDK~C3zRz2ZQi`OGamz*6A|?@AH{b{{Y(lp#5)UH)2|l%RlxBY zNnc!x?Hk^&z*qR;yoCrLU8s)65)>B>u{$ryD2>O~tbt=-bt=An zk-=CSa*f731?os0Hwiti1REAr-7vwVdDY|J0ZkRtv#@xt%zFXt6;=Ip9sn+?|FZy& zZ~;D35k86#;QX00H9pzhlpC(LBCm)D5@M*)pr@ZG2b0t(g@YptLR0jy5QSz8oX9rK zj;B%G4Gk!X5WAOr4SOquSkG;PZV>>loTSC)VKP5ItmSVc`+FgDqx$k+7;$uAE@ys`9X7wjK=FtGqxUQHpB{mbzJmuaecZFiNBPJH9x-aW+A|ddUa8Hmh++q24%O)*`#q7f>fN zz8}EmcsNRM=$iaD+#*`>QQmAYxr z&yvrm>WI%6Y!w=y&xS{3l%Iy3o~((Q)y_jwQ)2revFO#*qQ$AvDlWM7fIog5$x>9c z(ldLNAiL7$!hhmf5X1iY>Itx!yN0t^LnOOa@-(X1Et0z-*})PH11VWGIK;@61c$3& z2O@Rg{LU%Sk1?#of`pmoUupn@yKn4`e$3PrY*jzgxnR5+w<)25X;vTKtUE;Hopz`tmlDQBzw_Ba8LRp?1!1A7D^^`o4Y16 zYUjJ-%rsU05PW5HiylV0U{69ssfl*JD0SwsOJjj(1*jXP_sl3oUzCG3%8S}j`YO;Y ziPNiCwiUTem`mle4s9!7J~dn>KJTi$1h z6nPV~Im$qboO<9J0-aHnV@Idb=958zg^G3P4uyX?!nqZ+QYv4*3I8MV z^}GcL{gIa$RHbKqi87IjrT8HB3Otn+z|^uF$+MjGsK4LM@wWm?hLe|~$w+qD4z?5Z z;S3jg{#)|kY(Fhet-#BiNIX>%o7AiNv$uRUtEcd}Y$2<=2-We;f?OO9%G8WOF5==d zsz<=mrFW3Eo75l)qv}kCVJJwEu`yl-(=wZ1Rk~+m|xfn0wK1aRzR8Wtr)N95~M(Zqkt+aydm%RK@ONLuMAd9a%_DnHhyl z`RXQ0COb~1n0z(folmBsd^OyTBRN0tn4OuUPo8I}VMVUQ6o*a9dsgAUz^I;sVRp; zMgAOquh=#Xs#z=CP0xC{s>4D8vL84YerAett-^*tuGO=_s)^%t3~0MDm#qDb6@OP9 z`y!DMjeQLx4rIO>L*_EPQjbj57Au5p$_=5;H1O*KP*5lG^zI}!ar9mb|Irm6BHkaq z8Yea122SvWOEIg6KAz(!2ji%_FFYy>Vv<8yWquitr#8gArQvH*VsBgHP%m6IKyBQ~ z*cAkNz(Sz>jN^mdmtsESlO=%zu;eYjr9-@PpsD=KV?UwdIWvxe(Q82&!zy^IO6`u2 zI~sJQk?hC5y;-dT=NB$72M9nR zcxC|&Rb*yqcWpU!Cdv-9K&1OCgf7&ioJOW4Gf!OzU0ictug~T zJHL9uq~^+wBe^Q*KRDvR#5xt+Nc9Ge-gc$ZRRfMiOQIChYC1aao%`}e{HB3n<&I#w z8Y)a2=JJDE?r>&N{M_8WoBQrilkPz7${m4=fXm~maSlHK#mkqobP5XYkAmSGH59pG zpRkqS{u~DfBNL)eX7uYEW4Y>B{MO(h{i=OP!#dTCk)g)ezp$X2X3t`E?(xBctP6R1 zG!ard^e!JuUExdx{NW`e{9*A&;g8cTU%30fN&3e>1x+E{y%wIMv2X^AWg>WDle+D1 zAPF1|dvjxWffruWHc|<9%p?-F(Tdlret(8?=oL1MHbDXSgBDFjqk8_c-J&5%0LvmG zL^`36Ekq*DGI}}+IO1Unj2<8Xw7v^;x*h|UCzER}ywc-wX0ZvJn6_Er;#`>N=d)Y& z=wgMOM^c`c`w5mDI}plz9B$%xUl@+Yo#5Fgo1+(PDx>=D>6@J0x6)e+KkLw9_P{wdB z76{Knp=b3AW(3Oo;bUZCZ!W^(U_z>T`eO(T&|nfYAnG}s)RKx7^%?5YN>oO?8O~1q!8Rhp(jtpnPqV0Xkmo*|%cj3ceH!B=sCNddhB4lVr zeFZb5TA`OEp~AUQ)3(0!hB>yrwB`A*eIEs@(x&GiXTox@XGLCIV7CuHEWZXYZJd*( zzWW|SD?A|fEeK&nmM=vXwRsA={S1Et8eQt;h5Tx$cmV4z3KUPyk5WpH`49%qSJvDrhVPnxS}l>wY|F8&Y`j%W5_ zC{9Z8zIxv{kDL4*;ffmPO#?Uaya}%VpzY%T>cfL4QM7>t-hP76WZw%H7limi-_dBU1vC{&hId@v(6!=&H=j4j6|KcQsSRE`h(1r+~A>T?pA>f`kQmK zPIeZ$810K07HOB5v}KYOSfA*+IuKA{MVQo69(~P2ag^*E8is_$?bUU(b@J}=F{I})i@&-E}+N$1^|5FVU| ztEFRM;>0yeD&D1wsw)7nFC3v1DypANs->#K&~|LH3+78_?-AyO-)NW%lVE-uuRgOU zKhDrFe~S<>KZULU=5x-|Fdqw;u{@1xyIZU;BzL2F3t{wmfP+U1I7DM)fexn{Y! zfZ19sRc~|0LF%LN7@jI;DSVN*S5=`gDb)*!dcGR%E`>=BS=B%%Zbb|(Wz4eoI42em zo7pbMj-%9ms3R^~muEIrjp|4ALH7z%w>ddb+rTo)ePNu!4~1Rt$2lll44niJvFqukXdaW zGBmc_afTaC;fS^Kkv+z7I52`le=3!1ZrCz2QQTp$AMt>#Q{3d+>W(wuyzP!NO)qoD z84#aD>#_0{hn}*(irmSWDEU~uN0`+Y8s-~OPQ;v8<8?JKpK4$}Uc>xPM1xp&4A(GUAyZ>w{^~5b8&!PT z9_OzuNOpvR8SMXX$3eOPjVVGnSrLTDsNQk`n1jPWPGiN<)PGvg7Q9HiGJOTDL9|C9LJJvCsGh1fMnQf=7m{336 zLQ-Ky>#UD$Wk$PCNN`7Y=J(SW@ft+zRo=_w*CmXOKQS!gDiO)se0p1)4J@weu$_a7zQIDct|oms~D z>udsO&r_FRH(>6!GiSJKmUssFP!${kgu&nuYCMh@Ux^NnWHi1HLg){te=dG8sB=|6 z6@%x)UDqK-{d};_^#j95_P;*XpJ}F&k4^IX2vNyjq!OU-d)47AsU*yp3ho4>FCdR= zJ;~)e#)V$zi6;ng+rJ)rG@(?jir-88cg<%Nk?dbLV}7}JW*9!e_X`&}dG=xKqdX?mf zWFKo%Mw|^2HmAzon`m(V?XWYEtF+@mH7~`o{9d=IwMWPe)B9>@QlZ39NC!zP# za)={vW{vtpD3a}cpD{IRdJ)52{8`5ogu^6rjVhK9_AdfdlR6q9l|y5(#i7=SUGXHK zZ&Do$^mFk{mJR*9dC`Z0_Ry3_N0DT*82!!!x4|}?7>mL7cPnKX_;2$0IPPw-;YqUC z_Ey!y*$FUqATS@@ZRc|`sS_~gP(@MiseG<=DlOHq&>3&GSG?GA%?-qY9Nid=pL4Ng z5tIPLthJg8hV6p3sCxE&3U1(=3ObB|U!cT;;0|)~i)6n!lB`7%-UB%$u4 z_N%(iRMj=9s_Rh|QMdX!jk+3yK;0Q12`D~+LH;z)mo$Ax_JL6_ke!L;}0&Y^|3a0B-I-ijk<~D7*x~=KgxcX>{0|R|cMTZQAA3psj-&Z8QqYu60q%nu<{+65%z)OEAST8cA zOg;K`Isi$(3f(P|Z6yeeyAkT?A&3Xf_YcaMKb{wJZg1iJxSPlNL*u5A*K1xtzHsrR z`ihyL2re1SVqhP$Aj~!2eIyJOL*qhAgJL`;;?!tJ+~Zx{=3lnA96uwC!~tjE&9{Gq zK0pry{LlX(;6L+#fG-D&g#H2p{fK}-PkQ8PZowT^Z;A{4)ZY>Q%iq!Pe}b1a4P|uT ztS?X2;Qn$ynG1I-0Tp>^5A;dZCDx!s5`8j3ZPMrZDSM{Rk)+b#AJ<=SAioR;K(UMmYUPCP#)9==6^2)Zr9aMq#BUI>Xo8w+N?~B-b~{ zuJ0G32ltbc>l1c~ZaKHJ_GgX^Y02f&?eeed@|U+&egVphyy!g8krDEJY~ni_e$PoG z2Pe@N=Zh}+vGrequ|@Z`;Lj_I5f5CipR{NGd~`j#`uoU_%kfS(+x!zmtsZ#h|0MX6 z`12?Ye(auP`EiJW{s0aA9^}W?b-y3{PkyK2|4Xa|{0@Ik`4%~FUj^XqrTi$;CA#Pi zYSRl8RjZ41TYazFGksEj$NsG(vO%9WzG>0F_USWgn{;^C{ABvvY@nZlwJ$8v_CTK^ z&MChWf2AAvcjIxqB>r;f^IlBAefaKV`q=X0GAs~W@?-V}=7#*(7ef!jc_ouw^5e%+ zm;6{bp|$+zo?PFlc73lCJ>m3Va(!BUEI_3;JntWq1kaBgi-hMEUH;Uz%8y0)QAH!U zg$#X#g-Vfowt(0_>RLl2KQ6Ur^c+BT<@+f}@{`c10!(-(Z0(I46OuF}-i zrEq32d&5A_;xW`9o@#r-_>P+!lFp~~{XGB!sQKa7j?8?DebOyWfs z{z-esk9}to!OcH!<6$Q(iTB4YU~Mc*`>(3PR10T!d^L1&Cv46FXuwm!XQul12!8Ha zJUX>}*Ob6P<-2YQ1j={y@XW9EEXLcBAI9*258o^D!RJS$2renJDmTC!68qGijaF(+ zY+vIK#VTACxDHZZny4B9KUT;XjH1yFXb_%6z@xXp(MWyA6sxEE(LGbXe0qnVXZC0) zk4kw;hpls?)pJuRj5O7qxQvBog7&93iTaMX@9^qtQ3hAK0S^-J+%Nom-GdOU5kA|iPEHqxE(gfRQ!wgjD3;t5 z=HPD)jDa6CBJ6PF5+P{ePCj@H3VUJ0xGH!SE8woK0SCoSE$aO=SM@{MsGe`B(tlXU zPh0ABbUUF9I@jsunNED;{x?j7!smM7!6dLhjo#og^so0P{K2hPV;^8!=$vP9^-ltN zVpk;e!4vTSKeRr0WVo1~P`J|@;e|5julEsR8{s4x6V#km(SmFKmqhOw07KUVsBmfF z9F%i%>K`_Jpw@avUA(vxq*{8_?UZ$}Bq|0|4E6%15N`f86njan1UTddTVESaKl&&w zAPrHihN3KHN0?*6TnQsSz36Kw+HT@^N*F3y1uCjThk)B7>FEN(6tCSHh^{G~1n={& z3cMR5{{_5Lj{IHVoj6$F9VH;N0k1uOq?zXI?xVZP%f8x+-XAWJNegpPV=f@8sL^Ob zzGx2e*{)x_g%R0Z90dUe$BH)4nYj(VnT(<`+2O z_|N3;%W*1UaQl>9JOV@sOc`Zk{Euaj6v%e8Amwun1f2%eMJ6x=>Hz_Ht0YE^z}Qu(J%`KC`?ykUKs&(f8W5*9CGfzxKMIZ}}Aya;HqX zWZdOePYmio)xHosy{)q%lT)qNCgxrmydrn<<-SPy9O?L|Ro1de*G&%OUJ}T)Ub|x4 zxD&3pHh6;X&D`&iFddByo}+e7hK%JD`Q;=ar-TzPD`fVM;7_BIUXyD}?fyuAx!QBvvy` z<=q~S*O#1p1(`)X|2-&@{HM=5`;?#AVUZmEy@>kj(|>##{wvg^KVk1mSDzms(h85s zDDT(HQ~4@B_~ycKSY(ScFPQqnr6o@HUw92T2%mcW+xw!mhB!`dx_*M=Y=6WWTeP$f zipj1)ZN;J;CnCqC1Wsq3vatj$4RA_{kDU@YEi@&bcS@9~eXwq_i#4makrHc&XYc+8 z+KhBP+bImMKBn+wWKrMZKTf4lyjld&->fQG@TGY63SICTr{M8U!9o^%MG9hj9;ykB zP|uwYj9{;Uml22K|GM~x>>L`t0OgC8V#8?~-F=(xj_%yCs6@lOtP8TJBUtk+2$0w# z>H<`$Zs6~`dBi&rzle`lHflu*_Z#|LWZ-#7iDd5sN2WLz8TkwQVa`RSo``difnzBI z68XC&<-g3G{|e^s=H!RTLRXm9@j5}4x*CwQq5Nf`)Q zK4ZxRSAA-L)D!4tg-*u2o5c+cmML_er;DM_l*e8e48waQFT3O-jW^%&O?Vv3IJaOp zOma)ZDR6g&8!-8Ot1CY51Yh<+c`ziGb1Tl#S+`ftItY;IA1sVt$t!1KKih=q-4dRY zKP)b&1F&0zkZe|w5$rh+odK9xp}b9j^%Pfybly~jI6wBa`RXFcE(f21H~LnG zu7;1w6(*;k1O8?Fh3F0l94RS>+pPq?-^OM^@bs=~faV3Q9$+Ig={%2@`}F1SO*E_y zd;8egh;w^7aOy>mI3@VeUoo)Z`WIb$caR*!`NPlVEkH8Su@Iqpc(`=90M9I1p~Fi; z(=(>sVx`9VR{IJ+F+hg~XX)2Dw3dXPC2Uq$PNO=&2G|W{GO4X!ct~ML06L0t? zyL+s%%@+6`Q_3Ub+cZuwoe-yR38a36cz~ai5bmi=1Phgfuh_R}$BHb^Z0&TU(jfxT z13&!4&1S2He1*82M9%J89u_cv8|FL6eqSp@z zp4oiXKxUVN@B^pc1(`!K;C2*>!hdCz7XIj|oXia2i{v(TAGF1BR=}_XZUFjoka_t< z^-Sm!G*IzXhhPTB87`MC#^eOY=79sLC#~5@zs7;izHsNW!)Ym{sXKA+RS?M0K@|C*BH@bsC`o}D&X+5*)TbL*K zpsQSRgNFz}XN6#j6&jKi9+JVL2QKUo0A)2UcX+c<^S&ro5~>F6#w0PT>wLUoSC(8X z3>{$$7V$jKoWqRP46+);)LC5;6oTGrt&C__-szrVG@5CpM@CGydA-@ zyB&qeZ|fw)=~!jn7X0)hN+H})+&wGM2^!W=ad&^eXY**$<*A&IMvBar9DFZSZa0DE_Im>hV(}1kv^VC2W^bYub5TA6h-=QPI9YvvCp2}(XSrq!YDD?fjlF-J! z+rb^4+lQd8ieGVG(4RNfbNl1YA85S6;hWso&svgFrW5;fF<@yJ|}cM&_OGsjNox z3p+rC0MD-*QPr<_oC;BRabdSF?C%P7`Ypu&D|tJS=m%lR0wVIT2(}Kn;2@U*!puta|gV19~!Z*r}AE4YfvR#y)fvj90g7$ zsb=$sm_u*E4@03lF2Zj!AIy$xLU?YchaNoHaK}~57zaW22SNBI0@eeDXE10%&}Kjx z+6h92ey!M^Dzu}kuoAvk(II*sird&D6ZXbAf~P7Q0Ah|x{nlS-xO{#!NXj`D=L&p+ zqz$_r&M)8y&+T8Mdhd?$zho*S5g9FiYwx%gK8PD2M?zD6~mP6)i(ILp$~F z68xsV2uwzHbmUCGp(1B0Z^mVQLr0Fi;(g_H7p$)}Z=(Rl=`?Q}?FMhdgDKlPkumOg z0MMene|Cyqt=n_J0jr!qxD#V#A<)F?^r(16`s)j=D#3!_LXH*d8g4du*0pH9)&KBITh&}6s22geMF{CF(0NBAiz!D81 zVX1seMN(&U9*QPUZ!OnP1 zXe4RH7A)}U0$qI-zw8d=VL@VryJLReuQZbOhf=!X!NV}vTTB$3f`eup4d?K(B(e&3 z2Vb)H3$kRlSE6;98)GpW-&iZEX+zRdLbb9gti)qJ<1yS~YfQhO9||qYOx!Ow`+qBP zeO6J$P5_6)I(5!vqShhECQAMbHf8Ag4#JZ0##AL&|TXNJFI zllg|^%ycRbekgngFRaCI`-wZ5y4AAb1$;&8T=3z$r~BL4ABEPIVEY@HlBJg20vc0& z4-PxoQ#li@QVV<+qbqiLXl{#1Jb!8)zEXo1c^3bn3dgz-%k^nc$4M9T7D(3f5iB5i ze26{xym9Oz*2|bxPyvddSd)HdP_1S45*R)6*RbF3VA~2i@~*S-rOM8hH(hWPoqo^#NR4#8mYEvTZ^jq@>eFxGFTB5SIYO0- zi_ge(C}gvGloF1p|pNbWeur{sJhKQV)^VJ;5hKC675ySCpjTNs2DN|O# z7IWB8M;A$!ll?V%2(_4OcN!JsD-{?A5kRr|R#pywHmW{SEl;p495out$3t{m>Tzs3 z+pKR91Oe)cE_CYK`3GI!mVxX|i~80|WRn`9>ofc`f%pN0A3(#BBQ@c4nGY-gDGd+O zx8ZCN%RCZpRt;zXO*Z|F?a(Aa&6Xyc)itK}%O&LKn81ZSIwJ=BXCs;j!*`P^l01!S zb-l}GrVV(iq?rUfr?-OVbPdlX33z@ykf7oH2T@t${=9^(;CU0#4m>YPo<>#ZhKKqN z?Bd?DI4&$I5!l^-UI#B>Bwme*h4C|vkwHOFXZ($nQI3Q@f_J@qD>6J^{nNu+F2Uo7 zWz+_gH7VKNMTS}Azj~x2PPR6^0EThiUPi_ePDN7?qUnmF94ci>EeYEjq*m!*#Z~6uEN>Cb%6C2?7bt|U8E{#7w^X%$3Mg_ zLDavW1j%vb(yx26J~qwvC!pm4)4U{u_MiRZev%zEtMhpR0a6S{7#k>z0AZqv8BO^S z>kz}O6?0qbcsmx*i_}twBw}D6umYnTxa-77^zYXJuzRmkSUeIL_7>8OvbjYQ+@H2S zX96uK;QST60;$xO_wA0CPoIzy7)li6`XhshhD=F?KaFhU5{r4bs(XSZK)|TFvpau& zLpVBJL3pC-TEN4;j%OGd4ca!UMH3j;g7xZEF|v^LAYL2QX;Q99y;kiA4-zof`XblDi=kY(nuOa~au1-FBt;#+xi@u{>$f@Ra5qV6 zLaBVU9=W5R35#X#e4IUnbspkuZDO-JmuS($zh3&^tgbzaeQi?X5f+udIS<9LpPenW zz&k*R=gst+;q^~j&qM}cI*F3X@V^YEw><^iFRjV(_z4H_=A{z5A^0=F7sSp@k}iU1 zKg1XiiVX;{Gc^cbAS6?6mhY%~k6|SHuTr>Ctr(B1voM2k^=GEg6QpfaJCI5`{?$}= zFTzBB_^OxOf@6Q4fp@vq{Rx6W?f$HKO!$PrT-43opK^SmKZ_(?`g0ayOn;^r5DqaQ z9DtBj>?7as=!-BqADE4Gi5(iti=7s;V&^-f>#P3gfQ&BGCv$gGti(xeaHpD)_i`c* z&FHcBNFvVE5>;V$oEf82?Kld=UDX_;8!;wd0Ce`4v>jBm!LJlv_dLpugI})!zd~D~ zmA=Y9Us!gkq(;@dT||uXp5WyY)ueh!4mb@=@Z~Zf5sEf=xYK!1X};=U@=Z$QyEB38 z!Q(W&pXdz3YvkYAWPFGSKE1MWfgvK~K^7@| zS>7aZ04yvWmZ6F(GqF7`y}?^0#8&OC}FeOPvE+BLJD+V1}F-u zC@Pb?z3_Let8cVnXj0$E+#=Oqe5tO!;S7e&>Mew^X>@L8SY$9duSx#|vuFl#*kN8G z&J5gqb&C_%!KnxG&xu9dhDnCoab%JYpt5U|Nm|ZtGPO;8M7YBb?$A>k=P7mKUBVpa zBN20%Jsqh<8NwvPffuio!)gyXAw~qr&FTS-qNNWBnTX~FnXO=(*rLbQ)5QhPc1`mZbDBgHb?8oPvt;$4Ew);w zKZ`AUTj`X&T-kj5)1K2OwkY31pPj?04o9AH^o7->Tm~M)#V}S-RP{p(RDiv`I^}Yp zD2(+CJiS3A&6m>=X7P&lo>{hzA-C!*W9gl%Twtu_F#76fq(v&tsT0;zH55Z->TSe3 z@5MkxYoQ1#F%Qu~S+s!P%k0O}te)%48+bJqj`1u{MFBeCzZ%YzvA%g&)bVGB^LkOs zb3aO+U3gi3hNrR_KTE@>a9-*80GYFyufm1BSiiyTtg4Ste88&%`HMnvOne<+$){ow zVJ^d>WyA2l?O>rj~xhInYWBp+@>R|MhAe?{~ZEMNfiPT{#mbr=A3SIk{=OTefTBoK9{5U?$=rJV2T zH<8zC)SC?9H`I#ql*AvtjL&SFlL3Q1>ek^&doijmg+`i7I3Pxqz|sq=)J~=0yu8?E zqA!yD$$eTfho@O;)@>q!H4cm7AN>~o0%SaZd8mLHRoxN#u-`^|jq3D&|26_oHWh3> znQ{W}VKio>L@y$~@bMu&czEcwv8AzuFCnIxD7K@I4YRUZS z8?Y#yLvyGkRy#Ox1*GI3OO27d_@!q^3;Ynv#XDvBPL9)B<%p`IoVYGh4Kl~R5pZ;^ zxdMx+RhH3~+Se6FFm=w2ClOYilOQwJt4`vOBMwOTK}(G2P8$Ce+s62CTO9jK+>nO` z6Uyjn;H0E*)%0{I!DkfWg2(mq;W~PA;lq+L9oi;tEaP?R?o_P8uyO}vyVQprDHLne zMH@kc&^x_fks@3k^@0EYL5071BHbkE=hAWYB|U#A{9#6iV4*dxKJwR-mE zX}$3T9= zb+{rC_P{@G|NXmoN;X1Q6N|yTy`=Cq-5YG`kDGop`1Pr~HNOHqc>g^m6X!ihBA;L; zs)@ZbX!ed^79__i;2>3_qG!E-+z;p>CRnI1)pMAq%?|jB4!X-%e*{*;v_GOR(cRgf zy1l%Aer`$Rl-{2CHARKrc^;{?;wvjL@kN_4=Aav4?jFnyPXGI>pbO@Pr?Q#W&nA`w;ffixv$uzQt?DaQc z8Y5d*tjti;Pb7vY1p@d*IX@9$G)OUnNvfLQ8-}ss4fQ1*;>els+7&`=H)Dfov94izRGU&mp7)7f*=*d?BEDt%vkNGmM`}ryUzY0NibbOZl&Y{N6iHa1zC4~SBbqYT#I1A3ky}8o*WGa(de{}mPU)Nz66Iy6af7(xFbxl~)TobMbQa3cb)Sd5gR`c@_w`BN z;Y@w#QMi68oX!?0E0R`3pW^pv3pBHSr6tXJS)~#E$6iJj!!IA#QPllGgka?woU{4fhqH=e7UfP+t1?gz2AW$%beZH2h9_XjLLJ zB59w9BQD=-IDg~c5&qN-R&U%BFRY!M4l3nC{I6CAuC|X&+NAH0OZU3Jps$1PVS?|I zfNzJuDMUvuPyiBlFUD)mB=RZ{d4Im+Od=}~*=^ru=$%e_*S4fK?ISflD$zIMpf$a7 z6Ya;k;yoG7KR6IuHNL2@dr{z97#1dYsO|n=ve*5`#X;EMjRC1QPO;G0p^<&PfnMlv ziD$_{fg|S`u6Ev|A74^<5PUN`{4LbhEU+=qnQY>Xt!o_2{HIdA-6^f`DCTR;lNlj*y2o*1$%O-=opfBa!DS!PNm#6g*`4GzLq z;owGH{STXUq=0n4j8daa_z<>6@ROBbNqoah%^o2H9;r;y> zp5gO>*~Y17`FpN~9Up|oM%)SFqu@WM4vlml8p!rX(hl*5)~kodaz>yQTyB(f7+(~^ zuF`yCjxOV&Q>5$LIKuJq<*#b>*jjc3?I(Z*JZtNH@HDE z825RKt1bpRyo*Lxccl!_u{D^7l}A7?YXLS_6A=hEd#cTv#WiP z8{@d51pAQjgZdpDIM`QF+kt}Ap(kth(4NF!xFK*B6d{E>1E&<>PSZf-t44NG@AMVcOs&Pb7tI7-LleO#V=u#A!w=N=ghXeSsNW1S%^HT@di-^7bNtaY za~^th4ki(p2L*EGVa#O_-fF-RO~4CawfzD*iXni;3>D-7ja61?f9IPfuF9cNOM12b z?uL<`T)twOO0g}fTjp<*-b4Ph*Yx&>_qPd*X$VxD>ht$pjV_VSp20Pxk+gxa;}Z6U zzzTL2wX|x)&Z1yV;Bbh*4z`r)3)m?!(|v{Og6{)KDcDCqCJY@vsPIrL_&($Y=8Dyr zv26U=KQ*K{4XdccU|qq3u}Ifk92a4Qp>{STL{3Zk5x%fX{@D65M2j{F8bOL@ z6}UiFj8@@BP-PX@1*81fEfv-WN;OGpESOH+Q(IK9K5&Rt@oVZq)vz|h5EF?aqpKW? z8?1gkOZtxnSNAUq;%;#*?k%nfZcEVD>M0%qW>_<^M2*jw(UtDYXa073ip?h;c*rh?Jt&s0Q=RYh3l|>B-;c}DDAmU(CeEC)hW=sA1I99C{kYJDW$Ek+y9m?(tU)#a7|z} zMmz?79Y!~AQrOBzYbkk$o^^iysqINpy6*oK=`xTfM4V@RJiv!1dE7KaLHJ(qEIDQ1_`*{L;sG5)z-_UR9-q4Z4}ogyd1Mpm z+7Cf#Xq`Hsj!ya3A*QD!WWR`jSlLzUrmC4A+m)|+{g15;xVM(;LCo`J;g71{kz=H4 z^^{Ym$WI6x;RkNY9LbWCe8G{JWM?D;u>OpI*3e>u{% zU-ig-wdjnInW>5G>NYn%+r-CDBlj8~j=z>%vyuQB;QJcauP_k=`dZ;aD|{W8NKM`^ zb~M%_`o4TDa4e8<1l)#Fnb>EPM#dNR9Im~m$a>V&Y9>D8RK>wYVn1i&!}I2b=>2=M zzs2LS{2S2b{`NH7pVf`DiJy-~>@|K8{8H$U7Hoq#a)_|aDR7EoCP%@KK?{{$2Z0LY zk^Qk{5gzIe9>OXkxY^AePOV)2Xg+eG2jfStw{85i=9eQ}>$${!7drh_MFrhc1OH0e z?zTOC8iwyRK3x0~?rX1n4ZmP4T}xJ((hE!!+%J~ff?qgz$S)~CKM)iAJ@}=~{crBT zGtl!cv7^S%8w?x-x&M&uZ1Gqbuyzvc&>(SR84%t<}6Jm z%Eykb{mI_%f3rl1)v|os``>=a``_;V)HU1!<6S?#ceY;r6%QoZgRjoBxC0zoIiHwn zMo_C8>ADW4L}v5*BwrlmUrYJj=6LxV?j`M|e0AC1&_P$Y5cfX02A{pzA3jF~b1@Oy zT!Rg9LM-soALd*rsqOR|J|7x6&Qr?m@teK;g=?W%={)tFqSv9uO@V{1(o~#?tP1AG zmfMG-HR=(-i2>ISPrIkWS_11B49%(8rKwv2xC8QynuByO9_}jz*R&E$0>L~#>{)y6 zl)G65KYIM-LDn!r@PqXTcwY;8OdnM99pUd;4G23yeo?t_7+b>Pz32C6&+-hyj@pdx zByJP=*J{7|e|*2&aQ0r)cTe(=Dut8%+|WA{=po`+`Wuygj!*{g_GwX=qeY<=+4qRx z#}002$osp@pZ5ON%%27A&z~HB|Hb~c<8Y;Q3~labe_EQnpZzy%x-kCAZr3Y1UN{%# z!hbEtUrTx5#y2(aAE<*%!CK*q(SAL4vlIZ_pn&ahHj2X+*dHMSr0d~U&mUx++wNAh z^=y;i?@TTvt>SKNCu=&s7Dg1N3htL{6PvZohV$Y@!B6L^!JO zk_2&F;~n@2^SPmd!PN##Om#O4j9tIN16@AeC8<`gy=rf))*%vE#vlt(`zR8)zM}kf#`B){ zPq$08)oMJ8${)uol{I_6f3g>@dStXU{!`Z_Mh(vEp2qRA69M>euDHsAxf@hPrC}?- zgimH*T)7&L(P#yY2vTxAs zdR_32X&YYBGm<@8=g)HTdlq|-L;>vYj9&}85gS>_-{Lw_qLTZ+pRTu{t^QkT(u;fD ze=P6lp7CGQc!c2rq78f&sNB7%PxqJaN28t`cYlv+v%jW4N%+Tk{0lC~ZB$68@iWH7 zZ$(*xOp~jv`YbiR-TK#-B-7(LUeF9{$CfSmF-vWE-qovi_)+8U zf5-UgvlsBTg}*Gd<2hGPThNc=;s1{D)5qP5cIcH%|2>bNEq`}mrJeeIpYd~pyQLQR zr!f9c$ImhC*4GyO@xI5iuAa7P|GynS3*5Et5r6+X#!uO?E-bVIZ(I1wQj4E)m282( z-)H!z*6|aFu$zEy;xo?KL_?{2fjYKeQX8WZa<$J4QaoR{F3$y z^iQ_$)j!$3m~@Av{W2Yk_N&I|pMCIito{iT=Bf$$rx!mb>7N#UPSZcj0Cu_j9GF2zzm^LjfOEdbw9lQi^O{M1`>VY z(YbwBtI7CDcP-_>P^r1Qi+8&bYh23LsdHErOn*P)#V7cXPOBdTbkHho%PS?$ql zm6RsGtkYFy;O9pDll0o6f0ABG|77#q^-t1^_6LB3^h(j>AiXm5Ptq$(|0KO~^iR?& zSAM$a)egV?|Kma9jQIVZ91r)L?u>_%_|uGs!MNX&G#>trL^B>P#n0B`;Wj+f;f#j? zQfrfXmv=@m9y;}{9v{MGh4x0oeH9oHpJVV)-y`zw|Fh%4(Z5H!_O*aLJgM-spF<74 zLV1M`*Z%q|I$eZr`2ZkP>(mqWO3OGCsHbpQ2`3&6-VA&}2bn%<1l&!-9g}=E6z53D z`+pt#R@WEjGA-Yv_0egKh$=8qUWRlTUSCngpX!+FI;G%}tK1OYq^cVD)!BgtGE|KO z2dNV?2}X??!s}Az;!Q?O0Wvj`q7st#;hE0W0htnA{Vy8AEa*ROBJO?v*Ac#_Qdrd0 z{~%u;Z?pf>dng23BA`X@@g@U%USF|_f$A~Ejet4ztr0K+ysu7xlqUiPb08!mU<7X9 z5CIRBCn3Pq{~9lIp#MYvlm1U4cu&Q7|IyX|mA^rM5cfz21%eID1}(6FEP@7vWyAx* zF$^1u$1J=+QyXLe z)u?Ys%VY%oB*VW^4Y;)xf?VV4PI(pfL**OtALD26FD>!I7v}dQUw%jYG(O_S&px%S z@$(VLr(Tj#RHN3AmdW_}NW?>}v;nay3R7Tw=q%IZJcv(b;dW(2ELFf)+baoT9nYO9vEwrZ=^wwA>zCM*fKAmRp66_?sOjwq-_ zz)Jql^PY3JnF&OSe*63U^HJuWd(J(}yPtF3^PYF$)FoSlRFpW}v(eernacgp)PN=5 z1CL$(GlJ$E?6cBQYLzOtil6tsUPDIuGr{Q(ra@W#5i!XAjF(X$a7zJ?WAUsgI#p$+ z7-J6S@Op&{SK6n1PFx`lKz64ls7{?S37vZNR@JG7oK8*rVGg?JKLo>jyZCru?=Oar zasKi>#z*t^@$u-M+sDVTS77LG+|nIBV%L39e0nfY9m~bZKc)t8;=n6jKA+++c~+V1oRO4q*0fP%WD?t@U>D$x$1U(w zR!1J%z>U)ioKk%RrMQP`8~t4x7n-V3gdU)&%vMEHjoCD%2|aT)`aiR^JHQ^0+kYqO zb6gIF`-afKAxu<5z|c$>a;n2^co!H423NUUhVY;s!o-5;x7ho!#6ttw^bGRZNw+Do zm>BH66Firky5Dy(fTcfG1DKe@6c}# zr%!er{CBSXZs^~UkGp5dM<=}#@?ods9_6=vVX3B=VRaPY)OPuxW6n} zTwV{;8Sb@D8Sx}P#*tlg=MFrwTXXOmJs(ZgV?d>0YfyPTO!1O1{wa;Z;8yiU7<7zZ z07U$fe;w#R^U_jQ!lonDX~fO9|Ha9Q8h6?NV1hNW$4;G@zaWAYWFaSEpz%G`}< z@3$H;?O8FMHO*F68M$jz=`LBL`pyK*e0=n>3?1;%3*nbVF9JV?DbO>Jp~QV^yY#}r z65RZgSqW)|ySR9-FvR!Uqmx&`Y2DHZt8nRr?L@ypSXQ8cZs~;Mbm>IsBY`)Jm&?QE zq>~o@0+<5|{D_An6qqrj^W!m;xtl?Rif8UR$!b$u$wuBfYw_7X7_kt`ng>0lu5QKuwO?-gi zc+*E7TL+eF9Kqq^YT;#<1Z;fh;&mMoSzLYquD}3AboTBbfg_7hm#8lMf0{2@=>1}Fq4m$PY|-_(tV0- z53*Q?8u;fD0cEr?3=0j&WV8Gu%}n6Si4J)*vV58S5Y-r{d^!q2PDwYB(VztXI=pQ% zuEtFzO~#T(g}I8O$Bx&cO>aH3l-;h@?jIh(+{=43>jh$kNmUq|)dZ z1Z#*SQNZdGvChDf&3UEhD*{fk0?K}tMwCQeMhVdH8~&Nq=_acXQvahjmDFE{m+3JB*!Eb!YwnabL@8W8e6p68EekXQ6Y_rCjLJ^kJg}>0>2J zf<9);uM_gJC-gx9NWtxy2^>`$>fs+{iJ}p_Waa#GNrd8ANTgf3xGhK*(mjtZ+E7^z zU0{X#X0b-{(S?jt=z=2`y3p}wNF9{_q>i7-@8cr(DgTKh;&~2!!;G4^#Xfp#F9EzM z!{2J({4UeI6Sph*PmR%y{BJ{68iDF^LLI8ZS3jol1 zVPGpmxFRqg6)8|>%w5iHXxU6yt7c@f`KA!*O!|cPjfF}GVlWltJ_6L+PT;ug;q17# zk?^Ircfs;NdpY=KH@l^GQTe>=ZH+_udB`1DEfEV~;%-o~IM4clOY_FOT}SdGb^*rk zIX@fYIq=vS`k(hPm6`)bp@09#zX16USl=`9|J^PC#x1zDRLKOz0y9psPp zzKqbb`sjq%D zdTA)RXZ6dICdmCanSrf{30utfr>gfbA8?=LZ+S&;?49}0>%JaEEJOs+J(A8K;x`;Z zS z@NV9hN5|+bb_gw0cNjlI@YJ<%fis&kh4DoQB89P+nZs^%T}8V0f}KjVg=z_!-6!OpyRm&hnu+H+%3egki;ui zfv_?KM;v2hTc~+?p~kO9JKXh9hoD#{9Z1(G6nkf9*yEi3uou!D=~({$=sq~z3iibg z-$y#@VL!-^6+{*T!aYTgp+>X`@>+udmf4?^W{0`a8CqMDp#uW^u|xP`Ja8?fs~`c5 zx^Yvaj&$$Bcl+b1zV{1^CmWu`sK5x(`{DR=~Y9lQ#j zo0llOYIq(Hgy&!fo)2*>et3%i5+-K|IM){oB1ZgI=uhR^<{y=GMGrNT{qQc@xA})i z4Qu|mQ0zvsrOSVmm*()$;K_#@a|^6dC6#M4%iVX!@!g1$%u;VXkmm^@pN4idFf=~J z(a_j369)?LCwGEpew?q!0<*J-MbbaSgj~v{FTs=Ye<`Cpm+s@@`;jH6H_G@Vkm-o$ z@AU6$?8`{!y&Eqbjj680-qQC>XO_rhpvbaOWaHn)>QoKE(GL%fEa1Ev?%dY8a2^Qu#SK>lrOZp-FR zpS|1seho@fmq4IakVlV3w5CW>uWBXlQtLU^7O=|ECME!n06x6Gdp>{3Kf==Xop+Ky zMdxvvHf$yDiJnrGIhW_zYavmNR`PwwUBv~>o3}~P=(y^RNs|$5ve+8=A4+)VyKBqL z>P&38sHU*>wXk&&eVc21^V!O_aP^DGf{I{nArwU*e>uHZ z0p+aJEM-KQqhmP+PCSQWwDC5o>@p+17?6N=Ro7XCl10^vjho)XyXJQbt&cJ_XfF&{ z7&Cu~cet#sDb(Ip!Z^XTxR-8GG1f6fxuFKENa4sZ>Wp5ktNAT~48ROUl)ICtXRdt| zb@jUTY}RvFSFcdJ-LL8E1?5el8FtN3g!LUKwKt)Krdn2v$|9*yk?7qfD+HH2j7FOS zZga6)pv`-GhCp5$Ah*8E!_I!8f~Tis-T09;-1($%r2udVObh|stK<+5)Ht4x4#Udm zvKxMmiDTlO=pjC$d7s2Zdtebw1IH+Mor6KFFd7A#Hgp%V>A%?Pn&6!B8G461EZGJe zA{mMe7ck!(gHB9@3j4rclGeF~hbqbvZNw1dKQ4$;dsz{yFt$6wc0EREY1A!}{`tG0 z+lM(P|+_;%8r+!TweHP|hL@~^Yjgsj!} zXmA#S5uz{X7jjIi#IbRhMh8paMoUo1{eUn|#Rne-ER;T|LRzK`EA3%5bR^`&JSPASFgZ1JeOnw^qglt;__PsrU%6)gmw!E2dA&O z`$YEh5DeZk5)U%B`b}O#S{6MS`nmdz=n0fQ`gur{Q*h6?ln%Ci0wt z^FzrI#i^D81P-0CrK?v#!ITTy-|83oJDX0;E-fgZ^B>H^MsuEG3u#5KJtH;f7pUH= zIeHCdWvL$*40YCbZL4LrN_WT42NdyY1M)LwSUcut-iq^T?1FSBFzeX!9zPrNMiljo zq4yE?ipIdu=WB+Rs=?33ydXpW>c3-Xtfy1{&HPC=KQHn4c|jh2?r#2hU@D6qWqySf zEyS3Vg&xH3zzw`*VtI;omR=`aRr1e~ z{Eha}BZDjM!Y^!&`>N0;T@8cT4D6mY*kQ5l{?v=f-j`41{xI_Wo5P7VxCi}cf3;#L zAtY-GWGW@jL5a2-|4BYSf-XY#Wv8JYnJ%NCc>lnZR@vPA9V6Lm?9_;0_gdj>F#Ob# zXo({)YpaW#tA#vyi5VVEr&RBgMMTsLg@0RYZ{EsuT1l*T^sI zSj)Rs(rlE0WmZakutI6ATW9K3Rp-1cF6ZK+0PjuioIKo2eSIXF%Iw){4`-9jiwj#u zc2ReuGQ_0bOZL|o=g^M62LC=cKemp(e$8Isc{3TtHS}{AH7|nI*EV8Pai|bxs%vg2 ze+S-~vNP?2HhSFtdrtdBf%d7dXS~nVHh@!;WJ@uO3S5+O61V?i*hu8%z$1GD4yhmE z`hpsS+gxcRmLpT5Ff?DjX6&Ce_A?)CW7y_mWE5fVnuw<a{sJz+jQvEz-o5CJW<<)=))q5PD>=<|Vc@U|$!zBkFQMjCw; z!WHEAQ!{l=`6(g76b^4AA+tAfDq9BnM6R<9q1G#nxgp3K99{1?u`s$1?ls5N)4kwz z_7j031=h0IaX5htl@K1b(th$odMHtua&1?y1E6aAgRPAH!({=`Sy|?>rdBz&yhfE| ztYT>o+Vg1yE@?w)X=m?Is+|gn_bU}M$~Wgn@IrYRkWwlE6;K~T5~ES+$#K#XuJ`L5 zIcue-8>q0PJQY|2K4RZ{1;5BH@8~mJP6?l|jB{L;HkP5b9L`s!tDHBCM?%q!Va=NECE{dpUcxe-N~ z{?7MMjYY=1J?!{;fV}#%COXf)9)qhImy%RdqL-b2jCoI`$D*v0cE%(6@e8!q{|mIY z?)}{MzBv41J%V3T^VU)${x0Zc%CUGZHsZg)^R;8~Tw=su$1|R=Zg4LtDmCD7#DGap z9kUJ7Z{>~NG-GQ)p%Guok47?46ulBZ{flBT6#p5Ehf&;JPe*Nq(eL5kqS!eoP|pHG z{q;5DPWJdr)ox7;%hCh(VVPnnQ^P(S%=9Pcun&^npXqmWnq4T4j;n0r7D=u2`EA5y zNiXrrZ)EyWFZ~kJ_j>7vn4ayWZ(%y_rI#=rMVd4bxIf)The`*K-ml_ik*P~@r}_iG zp!A|8lUl$9!pN|7Akg|$G*slmr{4OoaxG>N=arF4S7elSYzpmuaJab>s}WtXx0+;4 zd?{9T-;GrNJ31iTyrd}8>C9)GEn!W~p$i!eFLa5+#SZ;iub8Pjr9Y`ZvoC1U&g_o) zBlrXN9Yl|4N(_&FWXwCe3@byURX5(2d6~;60$(WM0N?OZ*@IzV;%d7^g}A%02zMFI zK9e-nRA-&JjrU&ST!0tf#F&c<7voYuy6AeN0hZIR8w|~GSNU{&9mdXtwG=LR(W}_R zd5Xy=fyp-jhLO~jy$Ul2)8BVIE(=%;;rvkuQojGbFq6ZW5@S-(0lvp((Uu#pU27kI z*0wHge_0!Ht9bSRC z-P806>^B+l(IBF5^R$uzBfc6c5*L?OZX!OKI|{?e8~%;* zTkWfD2;Jsw#aI*z;nFv_65~S7l9ka{=~Vn1eDxSnO-25@XLdugy$?blg$R#i9S^Nw z7x421SkmI(jpk9O!AlWyu4Tq7S^_{Ozsx+#FMFBE$t8~&tgU(}rU`{ntfjz_t@;&X z7T3@_iYrc%AU)TYbkxGe;E^R*iDhOIt4tg1+yB*FOKb75ZfOQuT1Z%gpHN*wpV;vA zZo`q(jk@758(xR99mQ3{Q7U4wVHh>xvsutmLmb zgX*^K->bSgC|}*qpiac7w%Bd&=CmOup|ZsPLXbCiV3%=Qg^~CshNSebI9<461bDTl zqMmwMKUi_8t8Alt+Zy|Yhj)WJHSTZ6OLUy41&}8ZKG)d0tq`Q7w{1hMc}u*bMwqTb zyDV@ds!+C;v|!Jkw_8E_`|MQq=YsX$>CemAKx)7zWpD}~7@>DRE(XVv#IsnkX#X>p zEH>JAeiO*WZCNa{{xrNpbRs^#sh;Qa`6WCHV+8k0xy*J7moA(jr|azRAI}AgF{rs$ zB>6p$A6@_uG(Y?ik9qmwl7DW8A7c2*<%i=UvMjO(q9{d@L&IP?j^x94@V)gBxd0fr z=46%u0ZxU{Go{cRK5@g8KFNm7iB&(^LzCi8jz%ww{Ds@Mt?1IB zQbqpp6Hw%@irjEMP1c|`RG~3cHnhUC=k)!>SUWlkmqvv<+d;6v;R{&NIFq+XSu5=8 z=7T0NCmv)b2P+mV)q`(K)!*4o ziq%WvQI)8Wsy|kVy(ICEK2q#jR*;y}x1eCbP>_?|^l%AI%}Olb#rAc3k#BondKtkn zxr{4-M&dlw(mbjJrc>`>|6oS1^g4j9RmI;t9N#0xuoHhX*?Vs&j=IXVQoGqXx|H;d z-@*Lt7+nS$Xe&jUjq`nl{91kRSfyXp@E1GZmzW*1TKH_YKg32BdySO+fE~GBV$5Jh zg&M71_8(R6?;S6_A6%O(t;6R0RNeRWYvq$Nxn}eu25{|VFTjz%^j;VY?b!wFqJ3_5 zQ!imW07j{91>hNp25OT9bp+1iK$vde84aApxcP+jZQ%P%LRH)KVAcqJl2h&bm$F*+ z*+?7(Wa(-TdyVb)N?(a;7nHG!f6cDzH&>#F2Ey-CkoOjYfc5?*Dn!o#h>|+^Hzn4B z26d(DR;x<)k-o-JE9gOuKfCg}oQ{q}y~z^RxD14>Yy5`p=FMP~2Z}#fDN7ong>YK>H&;4T-z4eCe)jhE}grS$vJ52v3?f!cc?jEy)@tn?cX z0~fz8-v!9={52rQ_-Fb&-2xeJ#~-rC3qJqq#yeV?_m3Ab%JPl(o-<{P7!yQeqa zp9ptc9~kdrh+LAMjinPsFCvd#e!wS>`VPS0lf746B9&@+)Yl`DeP3ZluXF?IXcc}- z9}J4t^tTd7vuC2dfP6{-VLJ;yhgvkDbj(^t!mtmKHhe(31wuAQJl(aY45qgljHZZh zc!POg1`~wPU<#;7xYrE!ykh>j_j5eHw*E!})Xe{YILN;p(!H(n(J^$W+t(XJzKuJ+ozdW~xH_nVzs zZMXj4q93aMHTPu=ZnJQgzy6MkJmGt&SO0Um{!yrZ!E_=eC3X1}(|@1_yG4BG=tGD~ z0i+`{x>rs+loW{VIv{&2kBe5#l@yPT6V6$Gp+cCFtS3XmYsvaa9>rC*4s>z;rw%MP z5<@cTpZ`9K+&3Yn+*dg2eWZOKdCnDWoIC0d&XG}ndVwA_crH7=C;o9(8GQC{Jlp=o3$cG`{A|bmT@L2+>|aS}`}a9XX#4jqNof1` z-;&Vw?_f!2`}Z?Yyb;&-@9C1G?cZx8;n=^D(Dv^XNof1`$CA+Y@7Ey>ixZArT5wP?@7}4AXDAh4!X;-f3usaQ~Jtp|Du6B_U{3!6mas}zrW5J!8WCz zeAT{xUS9h*rmOYYzu9$N%DNo;_a;{b{t^{-WB-<|P<_o~|7KTyc}_=1qTU?)_hQ}6 z9Q!xBzTH3qE+SSwOwHUO7lKSoC|)+XAeHy z3OvC6#n|%Nzbjsl?*jJVkDmrPX4}8N${sK1@v9r}XldR*UQt-N`fOWL#(TsV|9E}& z@3uVb_uf*BH{1Tb$0v^#tPpv$>Kv&w*Zv*%3^TTG|DwKtd`du0?kxPevwx)xACL+) zkYs_z;mpX3GML_KFuC^ciO=RzL`xeW6tsWu&JrXsgcEU^c;H7a^WSr}z$~Br``f1! z)ON!D&2D@w8xJfF9FBJL*uTHh0%r&I?_F7g8=O;r$6226_1V8&x_+PiD|PwpUm&Gh z#D59)Z}wOo6up$oIfLYuh~DeZR0zwle=p0!Ici|yYV$Stsce?geR{@pLz{-x$Xfsby( z27}-J)$jY=iubPlt807dPF34OsLf~p>QDOM$`qgdtKU}s7+VZ^?B9L&qW$~LZ2MP# z^~dM>WZAzuqv!=>oxmVJmmhpApCC3S;kc zd#|gp#Ic@Te}}1Z$n0gg-TY~~zs3K+W|LFP5Ozt<5w=MBdq`t%0&zdCw>Lh+&E(i5 zvl-FSqSw;Py+QT`w%EJ9LrXu(lOptXu{XU5hj4618U|}8ETcUeP-dRpwrbsr4rz$rbZV|?sWg8OPnCfMH zqD@{z@1dUyI<>}rR5}YsO5(|>8$sNxm)SyZ`;(s&!I(jP#Es@?KYQ_D(oK72gWVU@ z?*fc)a_={85Puk+uG_)==lXu|9^8Mf{cp5G>wuH67=Y~)xd6SE*O-Sp+lWy!IT7Bc zSP+HZbz)N#rw=yTl}}@jAyo_q6xqW1%N=9{y51;X9km{cF*-MC@U`Rb=V(%j;DB;!}aj#{?>Op~Lg`gwM-d%1_qR_~5hM`_t#D^1!yfPkyKGPtStiRjOif zkjjFenGL^<%L7B4^z$zQKYc-Gy=t$54p~|4;lx)EUo(R374@h+1z&ytaS!faci`bs zj^G!>6U6T)%L0Rs-R>WJ0Ka+e_vB^wx=c6i0ocLz;=%TMch_Dw^#Pc{`i>3O_csW4 zkNC6t@4uhgsbAx7Jyu{qxNv;V!5>&6h`;vE0LVrJnsoe`via?QSbl8m2#jYEEtp*< zKV}Cj-#buw06+iF%8x%U4Z!c4Kl>u^+X?v*3bwZhcFT_O^<~MAN0$T!e_Wt50sQV7 z`B58eZ|N=m!TZ{C_&ZDf<;agu76#ja3`&}eIW(1oo3N*QW`Jwe|kL?ewU%$Bh zad#g3Lp%)f*dL)q0>V)Y@}*3WE;k;IGC{C^nk^5t|6mnu8#8qcYz~+oQ}I)2Cc2_i z>1H$z^&$j-!8`EaA~bYL83O{u#u#(=X@LutHL|3m*ulOy7a_H)K2bkAE zLkR{bJew)RH_FYITkYFfwY*M#EAuz;|HMM^gPGT`OnuWnFw+Md;j{fnR=di+;g#~z z9(=C-e~vNfyi@559rfzkYg%69vu`wha_pO5w`Jq!lKBqYy2X#fKiiQ{y1g9xrZT6! z58-s>7g2fWp*#7+E4}l<%juu+xAY{xeRJ=V*@M3{(3v2-y_h<}??jd^&`o%ERv^RV9cd&YX}ezzUg+aCb(SPz@qIr9_E%V5acGo4__jhr{%aA=Sr1N`{EB@Zi~ z$VS$<<<>4)V&&JOg zf88N|9Ddyic{n7ey*K`{!}h)`c{uyA?7^QE=!_$ul>YheOKX|7-2fkVI zJVzeBxFEa#x8|*H*UG~cXf+S(^-vzJoUF|Ai3s0ac(T#qngwLBt#^HpYY7|t`B#;Dbq0j-F=gL!lp z4p88H!oqV7ERX|eTkHb}S$z41D<5&jj{g3gi)FpbnTXHW6fT4Fd)OG&mKj9*0q}{W%&yS42Ngv7MyzGPW>@SzuTH|pOUZo(yrDG+ zpU!zbhL0P6q0H+QyuASyfGUdc2G_wkfAeIVeHg++W!Ut=VUz05qDO>`g@X_`YFZpNMz7A4 z173a_T8(B1CR|XXh-?su3@ey1tU>E_P#)ZLYV}_EK$Z=!EP6Tw^aNr?^KGCcq31Re z^c3v}K!mD5R3D%LS)x^7%?poNf9o+keDniS(N((yMAG>N=p57&+aoiiJNkJN?Ex?d zi6?MyC`Ih)FGxQ=c!aD~I7K-5P^hR8WT$?aLz-Gg0S2-C!Z<60Je;l9uyEy>|Ew0l zPI`+#cJN%(?o@V#D}~Tt@3nu7Tdw2Gjgf8AMUeqKqNn})y&~7X0YO}AEIjhm)C~g)>W8(Tnu_$sjYm&U=?FcJW$S|Q zuz%DSzFBWAi&bR)>e%A~6YM9n+mp~~FxSc9)aiwAH%Ayb0CCFR^@Z!ArM0+0;e6zS z$ih}f>?wy%U8y8J68VeM!^7-!1EdSV?1U38g(9r2cS=S*b|(Ih-&&hYxWn(##e4S>-*;Guthc7RjAqJ z^WyvGf9^59yOn?8>h>uoL;&=FLfv09#8&$yv;gFM&5?ZjD#?eCCS^6Jwpe#nMDg0z%Gdcm5;z!;@W^Xmxk>(fB}6x^Gv7Wzex;lS#$DJMoq zyMsWw%OQtxDhy}qiy?p4*auH@OiJ)7IP~oeWYhsQACghq?RER}7njcLJx666feia8 zycJTEf3orE;e#RsS8u^eddcqi6HmUaHme~pIwCMGu;UmtxK#>92tOkGq6eA7KIZQ0 zjphtEB6r<*;{cu`pYa(uZMsp(4iYZMCHD=jn*N} zd(?oR);U1GKH-R-g*-u&PJ|eoGRYhU`)|1xjrLm)dt>a>V~in2;%D0*l&4&#W2D#x ztjAfkx6DH9PfE9=Pk3@l%hNgQMV@wkpyX-wem;aa@__K*EoJEEdF-c>-=zNPkI}is zfKTkOo`c;GY{_T>kQ!M>kqne0lE@SUIXo= z)JgU0z?eqY7wS$pInW-MuRHPg1vY;~uc&Jd%<7K6KQP!EBNAf<5k%#T5xZtqZf!=@>8Fm+|tZnx9 z2lJ6?a{*qW$U2OOLpci>l>b5gSM~sGc&h6~5wN>5Jn{W?QfZsLuY4%ns`dS_m0Bh( zcIG$HJ`gmeJfF-nyFMmhjBWY5QN0Ae!{qP^_~%c1P*{ai#n{Gn=TIV!b~JxF;g3C* z_iTbT>uAnaM#zjQxvw6L*k^FH*uT!0S3D9#TW>wpR6D#dIs~^26os4LD~xTR`lJ2X z2NE>13g_+p#wTFp*t4@SXoAdVzp=$*y!jD?XhCM*2(CGY4+)|f_s4O2yQrzQ`Y8%_ zSY? z8zh1u^^GE0BZp*TNQfz07_MFwdnK$Ykj7P)i({|odIFcP13qU*lIP=!&VFt)Yon(`c!yv0%E`C_ zlg+ScHc>p15zehOXrhQsEGoh&O`-%%MBvDfw+TP|VLW5rH;^)SSo;~c^Nd1%pQh08 z(&%7V&`x!p361sgdeAzK=RUt#lPu8g5ij~5n7^Hpcm5VOd?GFT`@Htcckk~pJrh{dmOixjSm_y`(j`X4LeD@7H^o@%~mt_{|ntda7=sTXD zz7OqgzJ_Dm%bG&fgKDDd0~59^dPYQ+#rA~zK+i(MFHF89lI(*%g;V{Z>;3svqdJsCO!?s z2w=h>9vM9n07XakjSgwmQo@agj`sJ3W#Y;U5D%s?LZNNZJ_x39z{t$g+NntL*of~B z{6N(UA)VZltac8X#rR==jf{Q^w=SUBe*WOg7yLs^v^f@Q~0Nf8>qe@*aj3pGueN~ zF%#eTao;nBFU6!DH(lGXjW67Eaf9T$^Ubh@TYItgv}Q^uIN!uIYck&i2v3df4XL|7 zVlvzPf6obxo@N^Jikd>|3O1Q`LTio|J7BB zmqzy&hPkdQx@S`>gyp_U9#hvrSgHnCxty286~EDw;on!iIeIKAKL>HfL9F;{(1_@J zBx3>zRWm+j`;_)bVH;YBE$QfzZCoV;N-W{l0GOmCN$W zbJ{D;`dfZ`B~<-HvJ0{X50XugvsV#NB>aj>gndo_P^#( zIIQ7x(-=jzijIXEid1(*8x%g;p@|s#rt10VL5Q~^mkkwxzw}hgr%x*p9_hq$q4`d1 zh>fJHD;O^JzK36P*y0_}?|k*)TBBu!z6ffBFKv1#K}_H4(OuF*7}^756()0|`=B69 z*g!$(@e2$%wbkwM;!2tZ2b=}Hb-S( z^Xf&5rAID`gY$RHc4aM@6C{a?8JO`-|5X;4W0gAuhnmCt;0O0;(7_MG)f=LVa*zWX zCx9IFb>%AWdk;G(OYA%!dp~_G#;N0RDR|FRGpc?5DKn~*-&HfJg57bb-SO-Xe) zr~zn+u2gaWD8}s>RqfG%;pQzaDy48G#KpY+p{DBoBVv0g%XoF{{mjoDe5&!>r^Yk- z9U0FLFf7oIUD2=m@yJkq&jkjV{Bay%kRHP~%cxd@zGCI!VQoDaPG8=;oa%ojcsly5 zOP&~#kY}DT`S3+zJyQ|uAR>$tmrQ1syWXwFce@(jci)!r{RG40_!|1=j!)zbg>nQg z@(uKp9~-G&Wz2dJL*)Q5cQIx?%)~IPzApme7Da|}AqpcmjnRvJqd#oNqsKBC$DZta zMymfff&jufN&%xd)Byq3^8;wbXAb@n#y=Yt%pEfL0ps72 z!Pn!OiG~tBa%tdnyhM>0iqyjxIsq9D{q8_MZ)d$=%bTpe%})1mYwN*!0b-QoT`xFA zi4?yr$Sx{*-Dsw+E0xe@&=hV!GsF@eF%+uNh#!vibx1{orar3-7&2!4n3RhH5;bOO z6#dcY*j8iT_7|TvRQ;3@KZ7pi)t%grHRb4n5%0rSye#B2e7je~!c{)j zX$ZSW87MU|y625ifeV#qeh(Jo{3$oDCSByoOYMpHJJtvYmZ_Lu%001FzpP0=56>|twAXWh z&%2isJ{#?Y?|$x{G*<-a;#;{Yy11?+t}B-}0S|)lW3w?nlaRv^nHsuB@*Z<*i$eO3 z>Gz4Q0qN%fN4V_;r*DoBq_5COhv-&!1A#LY(h-!6NI$zYdN{B7sZj{Wc^L+O(P+oP z7Im}vU>ObP78={Qjsm?9gr9+q2*S76g}Ng^xYv=J1>w>WARM>Lhyu^<#IL0ig>ZI4 zuCP{TblHi43fpOg@?Jo#(|xB;uDpZ@F=<29-X9+R)Altw!^Q_eV4uX3i$Qh@jK?%5xO(^ z_*Ga{JUYGver>czyZbJRz;b!=i}LHgdG0QoU)SlEx%_(krHWtocsrY4v0gdYH3t3i zIuHNW#i06+fT+Mjn0+OwnC=o`;{Sa{1Y#fNuGDE5l8FsrtC(qAcv^ z=jUs5NB*z*c`W()QSn{umY;89c7UIo^_yINwk}ls{GHbnKj+71H~d`T=jSr;^U&?^ z^D&L=)mNaO=U$M7(LDV8n@dz@zH)wUu0QdtV_0K0KiBA&x%_U(AV0rzSAZn4 z_486@2l)A~`b{oBFBq%%`Ia?`p9A=m^(VSpEV38eysgVx)V}o~%!qGmGv>DS#ilaW zlhnjY|C<#rd_sg%(+KBHVe47k%oWCb83A==f@wcDZZ}M;Kr7?wE|?zhy_s=p$S z%O{|aD|z2V6q6*}G-#TIuZ0Ug$y7V@O~7JvlIwmO;aqC3=VKuPJiivh8ullcT_M=} z1$=6fojOJzhtCP`(M;QAm}cS*sm|zQ1XT?J>X$|&n81DK0V0H67}M^E%(!RcmE4>( z0TXUG?>bTRpF|y9nI~QNKZ%K7LFP~H*YM*~6`ajF(b=O`2Utj4Sr%arHQf(g&$&Nf zD);MorcO^(xkiDw_hE!Rz~O$}B?V%@)Dz#sbBWH|Qar-DjB3jp>>rFJ5E<72ygt?( z_JLWrNg%bE`BnDo=dx2v!!imCLI6 z#}phupNOdXY5V(9#YX#Q_;pI+3N~PPseLdCM-WZ?sI%ceu+o_ITjCkZ6u43jkT`Q9 zNqCE0{bS+`lf4_ttD}NgV%7xz0wTjEdB)O;<7PBr?dQmMa)vOx*ZeCHoh0JDq$yFq+&tq#@DdWzwSfC z2EsaDi7-goo{sYNT2cP(_Q$|y2BXA9e3^vj=vAd!>Z<_mTNMKaGI z(_RIsllceMqavvR$72{gwhn)p*nl)A-p=|P?{xA1as8^RvDAHqfy485KbQWDh5g#w z`fwqSj`J&H9|qY|?}tcu3SPA)qsMZfTc3~L!!0|_@3`tlFO`7n11$gGjX{>@Y6`hg zbA1Zg1REG$SRY((rj8ydkc_0tNAW(wHTKVEfnrhzz(r)Eo#b;h4tyRv%E$8OljX;Q z<+1(PQRK1wfglTE`T1Ea|LEDIkpRp88n5!O{FA7n%e^05@%vxgPhEZ=&kE7k;V*W> z?@!I_j^DxHAZWyzChMjx!xh%^G4 zTj;G~2^Q^{jo^>u~BxuxN12#FB8mPbfzv|nMgisWwTgINLs z;=VJ9{A_0JZU2hzp)Pb>8oz)zeJy0clNlu-{w26h+n1oc{TTS#+Yhp@7t^WTUW_ss zh$w8o8!sS-3hwozfF;B~nB9R|c zzOejA7J0%1{WMc&f{2$nM6A}>9cOOjbH4+zDuNO_}L)x_PCT1esA zMh;tzm+SG6m(b0Sm%Q7@^_R&bFCkwykpg>OzW2+jJlFbTII`lC!OFfxk9p6VC|jJo zg2I9e+xaD26E4JUm^}JE5oZ$MPJAKGyC2Ns3F^H2xd6t+IPZ>&Y=o}lo?h>``$qdp zy=p(yM^C}o0 zXHc-d+rD6ZIp;5zOb_(0uDkkZZ>09t@P*yv#dk8XhgehK=M55>Du2AfMiZ~~ zufFd1V_wY{Iq~bV<3l|EJP6>zH-iM=;7gBgJ>sQ9a0mDi1ID}DczV}dpG^|SO!lKt z??1c!5i-eY?^)GefFE^xx%`;Z-jkDd*q)-FYk7|j>=Ti%+qGx1)>o#Q$w$lALE46s zV_|+ghe<(wvMQV$R-e40#7zB!D=U%Yz2!E3?ML92A+2EDpph3*!{ZIB1i06TcFNd@ zB>^C>EU%G578h=;%gTpFa09BTW)IFiz?e!@*uUPJovtmP5RsET^v5ayD!R-ePg{#X z7JXlki{N&(RZ=&FCx|n=8(m_nY!=CrgaH!V^V=2 zU4z@R@50GZrQzYD22VcHm|Kx~5c;NWa9!nkv%0Qy(gSkWla=m*NATt@9P+EJvSW8< zZVe}Q!-9l*8{Na|NKZm9_4-j=?<+5xG4wy&|5MEGh z!1V?Yx5+XstQ+%3+}effc0lpQ5%R12B2WrAy0Cmgr>c##Ob%)^!7ixm%JQ+cbG`J` zuuJNkIpNLabI@r_Q$7wESHoZzfhD4g z|KP%9h$i|ep&&YK8&LlCXQX627@&rF;+xuT;`xSZG&ypu&!XVgqjEP|>K!lT3B+v?w4#;913J?ZWwHPF9q87?~yQNnNgg)Met@7SXqJYw_PfM+Nh3-0x%5uEyPI7tI8TRlWlW8u~jhfz=SKy=5kP_)CRWp4iuz zH3r{}Ocmc-o4PEt(pnud{96nf{{_=aPBfEwKcC5QHwwlQnCCRizOSX_j zC=%p1*)m5bY)&dyuHe$#MEh9W zc3{W^4DGm<V`<)3>Wjv8B2)u02})0d$y{gUooBL(OIou zkB3|tnz#Cyc|M$!QIyRbLiKUT#`r+VKA} zs}o`4p_?j9tL_ogx@f+c`teG9Wgqx?AFS3dJvCMQ#6d`#&FePU;lE<{D>-JFIcy1z zwa5mr5x)sWRP(e)x}vA!_^=UApfDEXD%+a3;#Fl2u|9}c|EgSDZ#`RQy_neti>ugY>S`zr zRV|6`O>I?Q{bBULQ2U~eq2dD z33b;kD-`uu0L|yjhm7VCNT~xF)6njyQloifApb0+03Wk?+Wdkj=S%?pv^IdjB0EsG z{uU3BWZg34x%S}csS_VYW8v|Aa3%JV5ysp>xERf~)`{iV(3OkuM4 zo{)v3O_?c62?S%-{-i}~L&VyI$TBQpB=&?91txH=&_&)$K!vgiMwLE^FS^3xcZ(#i zS|;E|!vIReYL8fT_uKuhBuuac-eKT4q4fBEkm`J~Uuu)Fwpxn-=+yd}7!RX%>F>oT zU!QnKy%G8giRo}S-h)8&#kq-1qm8-!hZ^y(JKt3yKUB5FNQCj=^}2y6v1CS-T5l-Q z_&j!VZF1T&&}>buRh9la)P{qj$&G%hmm=|6syeipABe}?23Y)v9|~FxbJ)fc`iWFeTQ+&ULh8GUlG2Atb#YRi($95splDjg+^-~y)oIKf z1CZ6tF%qYssOUjz=3|j?=E@DM*tqElbRX>rO48?}Q7N46hms1uPI*zdokrqrd?39s z;s*jcthNd0L>LhaON%b>BQBH0Td@hiAk;(!>8bowQ^aH}K~<$Mkyxi-KL`_N0#C^j zJ;CZsKl&Mq_o{8FM<(`(qz83v5UcgejKuEjD`*lkI9>hEQJ#~1NS|Bqs4i_z|4?dC z9PW(4X128mU4N5L5=|}rG9Dqn!%#L@fT%48b_iiKS5euPeiD6G{Ok;%o&#w04asP3 zMR~j>A;sq*r{3B|QmL>0z({PvS-5nk6vtvk9i%Qq4y>-awlpmd2qE_^yUC1b3{dz6 zGI?1&a_2{^Hvx?{1&R>NAAkb0$GZ7OVw?kQ=$iDN_{LrTmWt=2Iqy@e)Awa>6b?<_ z(iIyCc;s^~if+lKUEc#9ENZsM5Jeb6CP z*ayQ77*vjWl6B1)?~kQeffjDV?+)y#RsVMFsaczpJ>{R=(})_%+F*_l5lB|(0_ubt*~g z#Aj1U6^;p&lnEYlH><)@1(F5)DA^)aAW4G+NgIWHHYL9+RXH2@Ig*1_Qffo8OeLi@ zBn4i{7O4$MfmgCcsz6fUm26Q}OqWk-$n{6nLF=!EwJPgRRv6>uro2%mV9@AmW6lnY zK64+AuIEPI_i6O4M8!U%52t)!^!*uqrO`LeGx{zjxh+HC9T|ONQ6^yYY5t$BzJ|Sb zSFXMHbHBZZdpHC39t@1w&FLG!8Qt1@=OELu_s+(%viJDbv-d{vy+nkAy_dyL1bjFN z=-nl~jHFIFZ2^58W|!X~ISnP1(AMA(q-d+R0YG7F0ipm2kyavKD<;6N6q{W>A9<#l z+CY-nqdx-wDJQ?N_Et$C{J1m< ziE!H3c=&LiJ|EVkJdJrmgC*Jkynv0{W@*`4T{kK!;TIZ7D_1qjQ43=}){;!2DK zue2FU2$299v6!rFbffu$J(d1NmH5804NDNwpP1F#bfec=2wU(II4BK|q^1-?XkK10 z(wIB@Fs?d8tab5KMw}Mk^sR>(iNE3j=F`#N3K?^6{yIyTmW`!}ct1Q&-%V*^tEQm*^6>cLpGFEd!w&%s6&Z7H7+%C`PEyrujxUd`3iIG{^3?-T_{hCr&Z^}eGS3Y)Rh>rsc6`&U8;V$;rjN(xqAvs8x*Mh9ZP5otw!{TW)}r+5 zY{&KA$QlL%nPV97YSMH|T;OK4D;rAln`0~8grYzJ?6}TakSNDsB)F{anNw$CgvwqU z!MQezARhz||JF~_aVWP|ECRhK-h}B;*YoK!RkH$TH2Xbt6`5FJLI7A6VAt-!%QTla zb$^QV{4Ks^pdz_8Gy@RnJ`sY67w`>_UMKjSkFbsS+32D}+_D0Jo*4-XT}xli{+X%A z6)nSYvh_mh{xKDB#obgqqR@!%#cH#O9TPkEv^HBThjjF$MSUxpdw**%}#jj}le&lGt*>9fT)kTu71{}~04vHW=l1a4`3diKc z{z%fRbn*o>N2XO3prspd`mz)jyfL?~sS3&Hp)7HxTLR?!q|U#L#GH5;wn`2l;YT|$ zg2l5&9>l|FWLIqaV4_11K+5TZ>>xmK323MDm!K6fFk5XkU`!OkC_fb04v4WBV!x91 z;3Pdjy}nNPTNuuogYXMfsDYU?IK1B;zITLW;iH^7W04X@(}Q@x-5 zi}DCeHGE2s>=i%A19wfZCYFk-SgZ8YrAJMlnl9abB41IVvK>jc1a2h zko9a4jMO*g0P355MuL_#sXX&y?}GGh?4y!TKKap*=lj=TzD?iS*GP13ftTw-$p0+e zxeLZqs>ag@X@&YZUT%FS8)V$;2@6j_FJiHsbo>-7sim&jbYafnS3nJ|N&j${RM zoz$dPFsbTUtjzxY`yPeWVy9jaVt~@>67OjCF%l<&Aki)PJ_%KT@mZq>rw_wxt$$p2 zwsMe!iL{(-U8$1_s;#Oa2e7SD$c1gI6!O5kL4T{j+qzjN6?j_(2PiyPpxr8kkStS4 zDTL%um6SqAR!CCK8!)^oNqzxLah~y&VygW;4^v5{RM~zT`T>mGkDtVP7f-V{<-t>F zdKzAL$5Q#|v75c;9?FvK(5LCA&l*ZVk0d9&?Hi~v>9Pm9+#Tp2|1H|sPmrloFH7CT z&16=hw9^cy9hA4;_@GV|cCkRg=6HN{)Hf<6(*X1Ut0=s7j93V5OY;q5yF1 zUVi(HhJ*1f;RzPpd6wEqcz(9dgC_)99`Tpxa-n(h$9bUHKYavVJL{)D{WcNiyJsJl z8Sxo(vw}{1<=NiUF(oEu!j4iPJUD**R`mT+;F0E0hwkV*FY`NQBSqbRjUP?bq>XOq z9y!oQZ+Q5q#W#5nQkPieV&p-X@LBo@am%o?pS~42oIiF$--P!r9beA4(nsa7;vG0U zcz35i{{GO2CcN>p^Zd|5cz^bqAKrPTZC%0>wsXzHk-HK9jc0e`%HPaRF+E6+FjQHLkbagok zWa$zlSR}R{>pO(mr})NLxY7lcwJ38-FrCJ14SR8EJB@XaaI&u8v9F_Eyy$AENFNT6 zGw)d^?tv1s-Ut9 zJJR(3vO#DyRhzLXhKI^cc)!-Q4`uh3q$|$4@n+^c+#jy1TwnOi)wVuxG zquPHH?i*E$qX@}~!{ybVMh^s-J}-d#M%Ama*G5_=E@TGI|1&f8cEtLOR0lGtw?3q6 z0SNg1+T>M_81ZRXT|}RPcrGF2vhbWLe$C~9Dr^H|=NaxW7FN07K$C)@1y7p!a00=~ z{zABVblTrK4SOS2@ix`P&tmVI$!To(k%bszLAn?XD}Or=zi%N!@Vfwp4t8}~#yp~& zB<4H2;m^qK@7ytu7>QR)SGw& zxyo;p+seO_vR?P7Bc*$%((VO`CH{n3!{hg`o;qqwgE_pu!L*hcBOx=!)L2F7JK5yl zNnhy;P*devxaRHQHIJda8L`Ibp+(>(LEuDd*~CjsWqqL(f;8p*o+(gczL4~1lOwVS31&CEsf^uRPQC2W3bk#A{ zSbuzj@QGlwxbz6}8lHc~+%vGp*tx0r)UxQo+z(-A|Ut8v`r-er9nbl7cV5n|8myUnzK zG&t^|L(lH9o(k3PdwcZA+o6O1KJC$stxM4}jL#BuX2+rGLG$W|s&J&_HA z=v7MR>09ff{qXM@Mq(>!M+HR=hIIuvdWEswSX5^${K%Lamn}5}E?_@e*z<)e;hRi= z(M*jb={@TD0oJz6F6T~QZOhH&Dfz9%##6E&Jmm9m-+wz!-BXQ3E89Nw2JFs&hNh!r zowZGT5h8s*<^I70`^p#T8gjFEAYeSG2mk6?F*0eU6FtBx_|&QbBmN;qhZ5$+7%Q76 z{TPXT__ifpPV=K9UcQ3mz2i(Y1jX3KoQlSCc*s(72yQ@*%h#M?wc;&jUUT@{_XC0m zJ7e+k2DBU|vD_tDruC?-D@Lq))h9^bflpYz!l6GVhBBcf0q5|2WKBiyry+-uST=88 zfYV%@ht05WYsCMC*2p#o(ehGUGYzkxd}FeWZX!W12yA?fCHtkjvLyVIlmtSMln03o z5Nb}ZItzgBf^-^RcqIB4lqY@v0R^4)8d`r_fHvXI*V$2+wz%dF9+K%=>!YrQGKgKi zGZyZHS4g9+=rJgcTqAxoFu`2OD$Obim(8U{VhdXJ`fxo{n#mVZD9gy`oTS&|;2QvM zvWE0lbdu{As5`ayabq|R93DUzn?wn!s3YMPqVZ9hd+M;2{cqGlS|wSKJV+NHkLU1H zkp2f`KkIY&wLk--e}G@Z$w>L&FyhI;ad$FU(Y0Twn{lKqeKGmdbr6i{O6XJ{=s!#g zLNxJD?EIup@ZcS&F9cV>#KXwchQT}Or%)WqgM;{~4B}?ZcyVDo(%M9O!s+aSVjcBf zct|*uJj5??z#cd8U~6sfr{Jd&u71IY(^ZTWyBz%3VJv91hq_)s1uBCl2ZSxtyy(1% z`|&TqVgY)&A3q9xg`!vfetLx8V3(716`Q7Wx=?1s&&3O$*ftVr%=*$bcq{%m4*bsm z@<5E3puU`bjJVP8BfbaJq0`8DMd5rIw!=`j;Cvjq0NjfFN=J~W!_;FH{Txq0g8f}p zXJH<<3GdTK;}wtRP$H1GYq1|J;z={OD+}l?AF&=(+^z+Ki7&zBvqe@pBA^z>0&C2^ z&vE^VvW11f{j)XqQ}kJnYawwCtV}J6aOO67mwbT3auf&Y!-e8uSkSnGeN6fidN>9d zEr;ZW24oH&03e8XA`h+v_V5O>JiP`Lf!>wAOxK|%fxp9F zHzM1uTH~@n-qjWGL@HUG(NcVzU$t7fZ1Lq|!n-V08Z1@HQc3i(a;>!Im^+X$1?h3< zg-3&`>(XuLAD0H5A*>WXS0N=Mekf3>{cAs#O&pv^#;l)Uiyi@h{8h*>NSbp%R^p;x zxi)jc(Y=#>9d8t&I@@riq3dnBzv+5XMRoV6)@Z9AZm3E z5(iIk2pE@>L2A`vyQ+%hhy6v-5|m=}d8V|cfl`NC5W(7wIInS`bBb)lv(%JIlhN6r zu@fISYVkIFWXx@VR(uB>{UF{O@vpIkmV3(|LaMgq!SaXk3+eYK{1kYT$69McE#kOc zSU<#$49Sp;sSLw?+o&H|5f3I$WN)TYLr%vV34o$CB&Yt7zL=stuCwlvoex$89>VUh z){LTmkr2(uAu^}TUcZ_|7Z(PJfHc{5$XbFLaS8wrM*K#+Ax@iku##r4@(_=+ANY{< zLMM9TiC8D2aa15@L2zbk#u6eWVBrxOgbJWRpcJInLtH!auhE##^H|&w1fMatEHkr? zbIzkoZj<5T^bW;$loA;uqQ-Y0e~xf0r6KN)PNvbYU#H=SpinjRL^5twV;c^Ls;LhP0PtFmSjbOYZWhWX znS<6F#7mjSy>Zf>S7ZV(4azCDFCzOp=cI6wN{C$ZL8%Nv6FWzz=zGA+3gJ_ECV!%} zhz5~9hhKz3Bz9799@B(@3 zo^`kuv^+HPGl=R^POq!$22Z(#a$5wuMkX9yjl}gN-pTTHEr)@bD|V>tUT|Y2>0-%o zz^v2w41;1sg{InNI8g+2djz`@v^o)Q{O1v(dt&(M0jS!W4~g^adMqyCt#Y3I3}2yR z`n*3mQ#sG3!7p7BpQ`aoI*(kI=REM2dHxtP(46znUuJt?@0|8jyo)AGL7jDV$%r@OqbAL7o1*=igyAZi4VVr13c>xvebB~-NE!n#Dge82gpT6S_kWXj zoFgUUjd@a~M7CHLo16KwH=me%yv~dcKzA}pS6{5HM5c~q0f~=8$ z#91TR1+`s<^_Bi&RjQV#cuCSwdeQ+X`v$}V)!fpMSmp9dL+raen?Y@j&0SykiwNFQ z)iL%3&IK_1MYLAf>RK?K!&aw4k8zo&p$Y$tMI0I7ss>}=!C)$a&1I_A<~@p0Pwa6R zcC4U7F~%az7{#r^Z)%D-d1R2^?#|pne$(f7zvl4U-=JeAQpafi;nry8pAP?d{Pj2C zr!Q094$UKP|HtB6AT$xr7!O{kHg*-A!2H5BToBv)BNXigw3>(l0Mz!JArZ zXlAHeH;!zemT>d7A|vqto}5y3srtKWE$ACHIcb8vC^NBY1Mvj~YceK-M7DCyC}Rri zEIDw}=g?Gq6Iw*SxCFx!l1_IfU68&MNr!%L6%Q;Zb9fzQ2#xq0w=tHpoz)v3j7**L5SH26 zYExG>th6>D$j4Gd^>~Ee{q&!(iwZX&Np>=R@-mY1CB(Wu_W)@yNM_<&<|SGv$B_<$*Kh@05eCS@RFhBPg?{;}=`$p3Fv=F4`=QmbV}$ zl6uIM+1$nd?h}0q@C#bE4_JtOb%ajwi(DIXpjFIReaU=Ez7~4;Q65Tn`sw z6out23KM!r%3CDcR8rm|xlASHEt2e~p}Hqjy3VhxGlpTK_zv_0K}3e;(_J z{<$sF+MWKH0K)E>{^`!&1KY=8ctj#niR12JCNDCvN!}iiQS+R$m^`V>o zPLB^})8ixI?slJg4Z}a+uE}9b7zDfwAdOxEv2{7_>t=L{_;RdSOXMDIK|K-=c)jb>%F9yNpY}}q%_hD_-x(Iv+qBGg&(Bl9k1D{#-`I&KL^_ffl<(;B$jS^SwX zuM}Bt#a6);VV;OeaYdNc5PK8)r_edVZp>@|O+Cpj0@+Glg~y-ZjJW^D_K%Ka{D`He zjW50r{wa&?A240L6!9bMaPtzxruYQ$BZ!;ky7`dD*s6U{>@{W67|pLUC2~7d8X11o z{3e7_FlJ5=A&Rf~K_c+MBBWf6_DA`8+9L$^vgm0|&C?#n4g|VD?}y}=N4#x|+x2G} zDcrx6nT=`G!B=56{69KCa`3lcyhjWXq%a>G6G;s!$RGsA2ZbY(#q)4(exz0Wr)!Tr zZTzvgZ~^^G7JrOBjxgpOTWW2N?g4x+Q+#_n;MP>#5nJ;AvG*nLQB~Lf32R*N#kFW% zqQ(~LmRPHapw37VUt|JN7Na7jf`MOMKuiP`G>{o!90$>=)mCe?R||Mk^VNMFGm!0qVIKsUo#;7Fa$Ih94)0E2B8X$2RW}U-2+K^kq?-YK3Q10Y z2zTSX9{)I8QZ7jWz%4-l40xQx7Uj1z0RW6)C?*iX1#s4#j)EK;PI^Cam`uPC@~Iiw z6C7Dl;W{g}zmzmHc@*#TOaeoZ5d(uME&RJxw-#Rrv^blO;Kl<}>~vt3kLwxIkj#q} zy_R2bY@AywmV=Tm&v^EWe~KY^s4#?v{8*C|j^2+Oz0R>e4>#_G_|MQQEl&^B-&S|w zM=ssavR_rNaQ9Ss0IBWrnbt%mW02lh(N}P(ALHUObfdoU z9!Ww`?jFYJ)>St?r!rHxC-GsT40uWz9^H7{9W0MvZDg*nObGc)yL`a;ylDA6`J7w z9+yi0(CvfPGL)_pKIW{)9#Ec!P(072`$5pjohPHiV9Q;cN|HYIA?B^^Zt^etVvv6L zV7k3^xFFY~vfI98dFO4ENSAj;KkLam$2_Oxo#xsqL=+m|1L94dzSZJQfxgw^O-SEz zZQ)y6-!`aQQc_l>#hVI!tHc{Gp!RK5SVrXuIa5dK!b;BZ0&3T(!W8vb^CopG4QL4m z-H--wo5fNIZxdFwx?Rn0@RqUz-m(EDe~xt zgFyBul&!{~143CgISlOCQ0_cKz}amHWSqq`fh?4v<+10qJob{3$Ns!M^4J}Xx3ngY zNzT9RvInH;3!DvzViBkei*bqk48OZ3EkDEB9nQ~ir$W9JPUokfQS;bUg!VYIa06w7 zr%RlE#5cig3{2RB`D!2*kXf9H9#uj0CQ5_0+=5%af6~04iVMEyifgJK00o)r2PqOd zMhd@7%gpd>E14N!eO!lg4_eI3Kz=|*YRKnm)b4Sd_i|{-5S#s#9urtlrFSn_@vL|BK-bIaO{CM6o7aAu%gmAcz zHAAVGO1j`muflOE(I0DwG~j^=^|s?*5_qwBB;!G7y@LlHw1jP0F}fVQ@l7C;)x3z> zN9+*ZHw(z$8&3oBhXhUaO&JM(Xx>r0RoJz|EcX1AUO1tR1}px`&JLC zbHY6ftVw@GR3LH0Eu(ko!FL4u4OvV z0dwGYqVXo94`96fhJWr00z+*b0!0woic0J8(#GhBfV9g9MuN88Dfe2##rs;h2hx+q zPL>;Z0>m2tQ@>uXs9vUV$2N=~L%|1KsAuhHvH`!umTCAk(6EzNWV#fJbbJEKkiZ3p zW8A04z1pfRcDu(7QqAS)58dCaYzxphYET4GuKzA2sY>_2eg`ukps%VTbzCtVo2^hw z35FPod=HNdLD_seC|z!5eswMYpaHfl-BWG0EFoMEQCQd+-r8*MKr#ULu(t3gxZ#O; z8ou*}C&025zyd&b0x-?ySZo(B2euifZ!k5OR2*C7gZwe9B7i9f^7xrq?j3kc{?rNp zgTp1C=Wx-d?O#Xf_|$B;l1n$eeKTg#f_gNUg5T{_Kk|Oa{dX4xa#j?zg5cPI=Y#t+ zEZk<`?%@7ZuV;5w=ji~18aA7!Y_|!4lj%g!5X0@}<%#ApOpbKl?aAt~<{E@lYgXK{PFd5r*wH7!kK;@_5U@6306I7Jc&mqO2bIR==`@B}1r`|K4)Ix!{-UvCdN_x#=OX=~;N_a-C& zFedvn|1Y9z5uAX@(EeZK{m<50{*ivYfSxG$-rxrwFtarNk1%#P>Y$wHJ@o$0!AsZ+ z0{i_V{lxqGqNRD_4!4yJ>Do7O@IR`06?QnfdW=dIaPZwIR~+veNp!%L^vqGOl@_kFqQ65w z#Lom%;$fb-ukLK}H0`pJKEWF_lL-Uhv}`9f#P%@takv-i;hu(jk?P#taHZ%a`sQ9F zceNa^U;VwP-m{i=#0_d<3rOcrZYBVXzC;TNvjfCFVQ&gguIW=cWY1jg*v7#*Wg&0J zc3!+)<0(R~jWt%z7tjH!vb7NKllo&c+xhm!;&MsNTLX=c$c1$GJn2>$eUq0ghK1fl zPF*;U)?O1lwHG=BF}46brQ3^x!$iV`|9PkY#$IM%Cs4!Qbkc>dQ7-7hZ^De?4MV{g z((yiS;UER16Ez`8DsZ=Y!jm9s>X89}`6tTauN5P#_Q0tozMQ^%hmmM1p}?WoM+II; zH~yL$1O)fITi)P22xb&`&r2t;6ITHWM3_6v14+u`o)D&(?e|i{~%+N|tx* zVxWNP+tIi}3>r=&P+K^776CT>x#vlHy0f-)^lX{Zlt;7}OJ)G^0CUTK89)O|Av~~U zsArM;j{1l%^i!7!u}#0+{fXi9+j{T0SM5`Ng;hgf9|kinNIs01jVsyPDO>jk zK~yo+H0?aV@gLKKtM>2TtvqYkzfYyZ?BA!G4I54<;FqiY`%24+RhsOfRH+7}$3PaZ-wNbTLP?ty*#gCNr2e2%jChpcm?6&((er%3AF2u@*O%&)5I zaOPqU|6^`r;T=0x#W;BG@WqxBbPFe}jGxazq&UhXDAokjG~I@bkT~glir*2z~)JB8xVzBl8t6(1F@QFZjz`|kdkwy!mZetyrpJ}xBd*= zj)J z(9jtDtQhU9>yr z1e6_%_FpQAy{wP;dxDZ7EM0}B`>K1yt}Lp~mk;9a0obUT@8xA)T48cdsEfbl}Xd z?w(o*KuIY?psYr8G^)q6yWe2o#=d~HtCnq2drb5?!N%{9SyyJQ6hY%C@JtSRCnh({ zz{IXsq*+agyY-gwLEN5sx9}Bi=D@1DTdykY#^JGn&*s(xaMlc!i^8SS>-(GYR~baV zs;2=R=}iw}FQ-{0ckn~35;=p?irp%pSJZJ7Serfyqwxh?A_99DbS^M(Mqm%a zqFI2TCGrC!e?cB{m!XE>{d&1qQ*~99SxYnCvSK4JiSD%kswcnMZhJgq*Yw(7lmT{` z;eN2muM+{|C}Z-w*)HR)g6(Zcy>FyckQUEDVkjF-3+?#p+^iJ?XgNlQDa~tkuRoIvdX;K{k z`q9k@=}2@{`&IotlKslDU7HJG8hXVu6)SnV;$>E}Gpek@Dw>aV^MY)`W+}>iSW38h z99Jam{0>DRtVqKMO~Q^I(s)s14Lv7GR-3r-Ni9Dq=0>BHvc;9#>qc@oKsq%RtGn`e z@z}{Re3RpR^ml?Chj-v(7Q2(bz)c(t0@->#+J*S!Zyr*C7o_ghm89%@L9_?EkFLgN z+~LYE(e0tR6)Kp@+Is-1b0Fz(dQv2vsrkzV1{^z?+N1cDxIRO#eSKs(UKxy+y(Q>b zW33Hvky{a(v`4e+7*h!MIKfPDxKu!Cf1k(gxDObBQy>M<5SbHzmH;T+wA}3hiVb<4 zaZ5VZ+1mmr(wJk#s4lwnwPTU;fYo)RDgv6AgG;rsys^PwdUNS7*83UUBgt;k@mXd9 zGeR>v#8tBglOuI7QrXm?Qv~Xr)sCF78%@%7yO=OL1!A&eC!Gw^9 zlI0$dIWpP)N$U*)MTn2*t|UHg*S13=FuK`x}X%@`E|Q zlaER+p;ZR`7(&O_a4ASKnJB5GdDi9wyuAXX<}F!GvrZ-Xd|hFzq(Gv7vG3kd^K1!L_R0$OCD?0 zAIC0D2QfWIXS(Co2)_N*l^s*|2Q;jENi|?NBnvi9g1| z2C2|w|5YPjw$T1RAPV{IA1xXZNU7LzA46oHZhtk3soJ<{QYOxdU5ZxBpooqYKt~$< zIIib`Pkz1kA)Judz&~-j456x%DJ*Gnj!8j;a*mVoCSt~(CGEWfMg}@F_rE{~Y zJ31e8$1}zaYOmu0>l%<|U=Cg_lUGqy&-nQcK24*zq;EubD|Qq>i_3BdGJ*NGVizM> zAre~*%^gu3|1ezlWv0tC6lU3N2p!n2rp(4r?98zBoB3e6*v64HzoxLR2?g zADBPEodKp59Yh3DNOCkT%L+e4vNF_%Y;RUfWe`(@0t+5XVdzoZ>k+nYTM)KxpYJkF zr@=?c7j;eO8SMJ7km->vEI#~`;wbdQBVc|)f3HA)nSU^nr~>y@y_F0{;TOIB9=%7f zzx@edJ^4^Kf#SNqU!fYi5bap8=cGx6S=g+z9z&G2s-dGOYEWB{SN&tG_ncz^LvZu2e z&d83aGT>?CmCveoD;!*b07rHTL#z}syT(Z@=I3h2?g8@B7)@2mLp4kgAi-7I5MG3y zV&H2&l_|Xv(9}qcy+Ft{jW)vZH;__{pvnu+#QO~AsV`w2CMjUzYWrwV<qP#rT^pYH=%v-Kr zA7RD*5351VN305$bhhvj2`Lt@Viz3IG&Y+&D_h1Oqwxkp&LRWZfPtJb`Cu7HJsvVA zuu7)Zd>pX?t{@NfdZ0ssdd7J3E3A{oJkN(@st^R0iU2&&JC5LVa=J;i}=`Y;v%lS7Up!A#Fo{f8gv`^NXlU^DaYQ*CM;WP zdOHsuHRIjOQYy?*eU^50$5VLZX;NA(9O8PQL9`xycHhOa)bezqqykH43Q`I%uhVc!5SiEmPG5|cZ@C@HK+!#d3z~eux*al) zn}sMT(6|EkN5C2>pTZxsd{FQ)O;N{*f8(VlJVHJGkTTm=G#?Fy3p(^fbOkE+Yb}+IsFhoU zWkU3zl;93Pjp)tLz$)Jw#CRN?Hxxt(oi8i=*or>MP_tkMu0wZybi+BotT-{S+ZhEN zu-N1LUI!dNDguiYYev5okH6$T!IJaKgGSEEL(fSiLxt!o8Q%s@11K7^ z!z{*2B)354N(&oM3VWR)060TmNAS^WC*bB0AJu4VAROI6vA_AGq&^lOFC+a*S!%RvHTvYyv_3GlUgK7;7Zfw zuRwWsf7Ig9tLHgc&cx^GcUib7l!ZI2k&X+-WWlsyFDl8F2a8I&^0z8J@bHBtIVidY zWH}qJyXn^i2_ARCqXKog9+x4xTvTG~-*Ww1!QVwCmHb&)G9try%U0A;>5890?7?Z# zOYzp}c>~}4-0g^yEyVvOdtm`fk=nfeI@f_65i=^TY`pGlDvjQkLfila#Q@ew1z zw4RV@O*{ecb`MQO=r__@ASVD~guL9Yu~*S8Hw!fx{v1H|H`yJt9FS&GC9^gCm!)OJ zJ|n_mKVbmnttd}!k;PWb!&dJ`J??1ymp1;5!TQy5Tf;}D1(>|k7L%)X12Liv5G+lC zghbEAI@QFWK-8OBfZd8M!@z_C_GguhDligCjOk!>fMf`BqieHbe@tr)nAh!$+MYR> zs{MeR9!>fqYNW;ITr>gE5F(iy!6hVnw+O$pP(9wi;ob=-1ghVc$R7Zc*P-Yjd?n!0 zmV8$1eu>^tzVZ9_h4e2489u_JaBa_?kZrqiRI3m=R+`75VFsK17zMA?zId*3-nXhKa&LWD!kJIwfhWV!c?&>Du>Tv-!TFSoSU!- zoRDikJECmji$b@0iSug|Xq>CroBK4d7kKZiok4X|rA*6mUP9g$KK&b>Hm>K6%C)l4 zS2({wwPG*(e(XDUj7G!BE>CP``&cv1mrV=~kyJ|mKKstPzkrr}Tl>zR<)d$7-?{73 zZ)4v%{nTyRcgDPzYTxPhep~jP8!y^r-}&L}J9|FO1=r#dUe8_4SfFAj+GXDnL-7Bw zedpHaKwY-OzO#k|C1BrKdFHpW?>sV{pZ}2don`OuvhOHE)stYt{}c9|1C|hpcG-7! zo_%LNt-F9|Jy7I_w(nG|^z0J5>^tAVzLUMegX1py&My0oQ~6UURoiLbiO$(U`_2V_ zYs0=XyI(W=&e5uT%l4fqDBqHO=fQ`lhz0CBUsnB~_MJOURn)P$eP__+e6!2Gv&+8o z|CfE|jX!Wju5cdBZNgUZ zq(nQ3m1Ev@n1^vT0V5<~SniK-HQajP3JjA&Fq%Fyg;8O0MhefUn~EE}$dUanEyXIa z11-cyeJ}09>f7vW2ezMP-(uTVcYQKUk`Z(~u;DExKJE%bXlbILEHPY8T*<~QK~!ib zr-&tb?ojS9oNp(4Wg`@jhTSo!jB!D={IvnUN*cRHQVgW`Paw+pQUDlf;04rH_pwN5 z1W#Wci{gM494u9xn^Qf;xk~aH@n{@4oQL}dH%1Qsgwcs(Xg$LxXacWca2Rgywy-R= zwx*A=gVxOh1jc39@tQ2Va7A?|=c@ZUXH2!anL}_GUCP0yMVMqbKVAv<@TjAF7okbmQ_JF;`zAXaC&k~zNjt}(Y+ytSIhc;lgY`t~ z_0IK&YqaLn%3s(&4}!ykOC zp*(IV%;_Xvqcub_%ak#_T`iu-Dv?=M)5)cxrx?@8=*(`Mpd7>(ptSP`Nt@)&=Sz4d z0SS@AKLsaFpUOqfzT*jL?Sq4jnCQsr$CSlavov;a&dYYz9t}X4Qt=vebDYd~N2l&y zf+&5O=Q%yzrrlYfsw)>%(SR+Juhu(9A&f-9GxfY82ff}2@qYjMB{~Ot@cUds3!RVX zqiV$(d?H*whstifTe@Hx>aGjn%U%-ghY*|-5Rm~h`=xlMbgkO!a$1e{3$YM&b{h_k zdi<(rzmSUddyj^cq_V8H5$y+OB8VJ9BbB!7i2Wtmt15qO{suAlgAfcw-DrgIe!V2# zubdC?_E&8M`gKE~AI|_lpdZqOpL$AnF$7PtGdCg7&-jxhYRRtK3Mb)<^f{ZkXT;T$ zaIj<7heBSxeKe91cU6cIRpJ_fOA_AjaQthD&MQ{Yd6%L_iO#!Ns+Q=y1&Gc=&nEH= z0K^1H`;D)8{V^Q~zl8VVA&UOK0~#^?^}0hBU9=C{_x&`t!YOKc(-qfxY+k6|VAhCL zS^?B&h+Yz>cyNYl0VLmu>q~2m=1#lYVUI*gAA1S6;=5O@E?#{oP zO9)X)97;Wld#_HHS8Z8r6+&9EzZ1-L*Odcsn z+ZIVo-t!Wm5=tFE4#uMk+Id<0710=)K==reH`Yr6SIo%6hks4Hi;lyPF`!?;;W!L^ zuoKsb;882HvZ&pQKm_==Jxr?x)S04^1Qyu!WD2gY3k+yFuI(N;fW?YEA`N>%dtZZq zNQ>1!BdRr^CI;;tLckPYDW^y7Ie)$vWWqD+p31Pjp{QU;1`q0F++Ik>?d^jFW+(b} zwG+K1Zm-=5t=e_3LfoG0wpOuaDtOOXRuAQ^GrAiLxw!%$Fd_8OD{;e&7Kru=6=1XK zpCukZEs!Gk%n`SmA6$SnNBY4KuL4+JMJu@ywbz&O2AE~f1^f?1_#fWpS}|^06QEeJY&_(Sy+iQJszU^G zJ;2NZv8M6Y#Lh*M!>}7&#{iN{mlnQZO?m~SC^Y#*;DP!qe*=#i<%bK43Q{5f8Fpzx zJw7A>)42)&2H7)1EJ6G)y}rhG9gX8oks!UyUKB*v)l9fm?$Jrl8;d2d|!GXa$h z7n=}KU&m(|mVGp(WejUEUf}p=d)XF_2|n#(vN?1aLj&KLA$!AJil;JM?yxJGe2iBV z=@mF$)+pn3dA?vu&>yn+`4jmQBE7;0#{Wfs4BxsT(RANOUqnTzJfh~OKG^btb*;q; zG)RF@8nCrY_+kV{AeJ*jS5!skX)yxGc7k8iHC(=W0%qz3Ybvv10})7}c))%YNNF?WaI98HphGEPWMTtmM=4Q6G)swB$|iLS1RJP`-{6BoNF&Ip z7%_OMJD-69iR*0lHeibE*FyZH8RAof8ZuD`?|v3H8nv*O-iodQFiQ)!TG4-{$JyB= z3Puc(Z{Ol_O!Vtk)uY3;*fEGN{YHinY8q3#bt(}i47iL)SrgpPK+hB$iCqgk@ame- zSML1v8sUBFdJW+bnbMI9zG*5;>_x;jSgjJzao0cblZB>)K!T?bL_xN$+1lRoJ@fE6 zG6n#(<`9ukv1JicZ^R`EewprXBqT!^;@{?ZU#Zj<1m0LVJa0QAQbxF0cX zsY!n9seb=e-U)@~Kzcj5-?d1klhO`C@E#X%`|ZR&m-UF~R>tO6)twGL%CseJdvr*~ z+Y8FTeq*ZYPJtLUGVrzgHouf`CbzRNHR8Q{w9HQmQ5Z`m5TpS&$%B;0r`L!wfijxR zX0c>iI9iNSq5BxRg8n^U3q*1T-a=(_o*ysxTshd`qF&i}$=|N>8(*`qS5C&FUPq|A z6+krT+P%8zLV4qu+cRGgiamJusfJY=N4>iy@($A)F75hed(aQv7mOBUbM78tqltss?q6Jd) zE>+aTdzL$&^ysLxCbtmvlU@*mg7av_X5mf#5|v3HFv0GF=~!_W5K!c}$@n4=3Hc6s z?Y?0afbH%s)J2eOl8}D6T`EZA8C(RK}} zNAM9_vF1A^DAo8PO{tGg?VRCEx`Q9haqh*9*?^qY=-_+j(OXr0U+kp}CTaFX2i5Kb zd&%^2w36&{^KxY`neq02fW74DKVo4)8zN@8(_hlK&ce zN%XW`_LBc4d&wIxR{oIolA*f2dPpC;>?NSE*pc(S+DksiKE`d@O9mg`hP~w7r#-93 zx3rf$_Q1M{@CGlu8A2vMUOy*q;2m}9ZR*<&q@4ie_ZoL=Z9TVPRq zj3=n48keyRd^8eC5vy~@bf_8Y`SlQeWbJ*jic$OPwGyLdhQFz$42UiEly2qLdWtlX zs;Bv@U`jk&%=$M;MoXA|?f7TTZqJb7sYgsdQ;YsHiY=1ZEt2eT0wUCK(4X5|f5^8C zy<*Uo#G)O9PBRwmvn|Bva|iab$A8>_LY&4YSr~C=Q~4Wz%|xR~_!$Dt^c?QSF)T4>iLU+2mH^Nv`Uu2C}r;OpPsU@Is%PRoE6T+jVTkA zK0$%maei~Yf{5yii9NeXW|pRP6Da^NIG1+h8dv?c_C85XWT$KNM6!oWo|)qKVDs@R&o#0zc> zN$jdMTMb5%J_2lr9MCQq+~foUSj#V%ry~p1kI*a5SG|nv6;4qorG@fUi2Fl)EZg9e zp);lh>A!A40%=zvTm-)KK&&e3MXN()eE!v1xn}zg?~6%>-Wo z^7OwT=Mcm=s~BY>R?kvNIW&6JxCY0%0CYv1>h^jZR4tOtDPjUt8UZj*Pnx|Kk?#c z?K@$kKh&;Uz0Rro6bz|5!f$o`@59f7`im09&!YY>zd_bc)@2$|)w5<+dlHd$$U2*` zyFvs2M!s_%tHA~fiD$*}A{mHjm84L=LH~4I>sXjkbzCdr3HUs|PN#!;_)kdcI=gsg z?YI}PDvOr_o=dqByG0VodX&U@0y1L>fogSuQz`LEWr~~#EqSem_`KQa1VAEKcU(>c zt1Wp+0Tzyb=!8>&${H7{_zA|(j(P5zCU+3l2>fwnaknhl0f&>|AZ*6Zs&sd{4MVbq z>1OWbItS@Gc>|gI!bW5WPV-+Z<_Ht`Hzunr`P5R}kw%d=MdGj7I0YIZIC#WJN6kRu z>qC>r=AMH`{U;aY_H%xBGKQ4w@&FPIl*Si1U*iOiQYH$XD3u|WtWPr6DNP)lD>0sr z!I2(GT!NS)gm@+{&q98nTr0W=-rYp6L8udaqZeW?#roVU#EXJR=7$mFkDJJRhM2nj za!V6^5q`URLx;1oYW8s-!DDNBHt(#cmlUjULCu;-_YdtD5`QqacO?02ZY@Wb?0;;F zY_c?2l8wL@auVCHQ|F1BI-*|{;rQBc_XWkMYsy5_5o@5=kSnLivXPzcx~?GAJW}S& zWaL;6>+_eRy0RfloL?bS#+u#lh)AM$*U}*iB20&>sZk_zHHQeEJ2W@P$^5z#K-dsD zS~2qo3bB#0!o}94_X#&mmf;RVr?5}y=D%WOi4vUdQVlS*SDZOMcl>)?zZ>Sa16!!H zV!xH)zaRvycXGdz$ZDpc0RO;hST>_Hc_v1`nFFuKtusB*k@@f{(bGQJk>t?av61Al zmw!d%Me=3qwz-kQ_1141BHh>LuRVM&9}X{_4Fu)i4U>^E8%Z#Z=`i|dn6D1UlnyD1 zWkfQarq#e-13JTq(W zuLP7Z1O$p{mg9w+E|pj6+zXRxCv+nkvoV2ra`*+#z!*QDi!vo{Khhm|vcNr#!9N&7 z0q_mcu_Bg*&~s~cwiAUJ-ep3l4J8L6qVF+WMZlMVOPp&UT^{3e2&;*4ewyrZFp_mR zn?lGI8rmz8Wqp-xJkDScXP5qj4Csyz2X3{3lfu~H5vv~B0EV54RuRT%QL`X8eN}@c@>i0wD6x zeLgOb-*|g)q(!|#ys3)!Ivm%wyjaw$T>p{*_~8%8m-Jv$2lQN&03`W&22FbDUFT6{sxVS? z;HwjU7L&@r&(2ErbnL{*lA!n-kYzj31IZ=9%`V6)=xjw_AulV@ysSic8DLiuk`-WQ zeq|SaWlfv~qGL_RnKFH_>^BJ~+t>Xg9!_n6mCdVJV|V{b*yjWki6mdht>wUy`~A2j zg-_G+2sR?2QN>G~4`aab5^%DAgu5>+M!fEn7^+C&WS=9c9XZ(+;beQBWF#vgj$~x_ zjUpo(2u9WijBH^9CxLi0E)z-v7#Vjz<3D;5og^az-L_))>A~0IVQ<67r{RWwt#PuCqaG(K%Vv}xIN2|5{(?AU zIGHP)%*|hAIN6Ja%aD`R_B^b^=%c{L4#Uw`6C;`HnpU^O!#YeP55sX=bzgO;fojqs z53}3kVQ*<3_B`gw=V4>X!~RJg_Rk;>+Z^O!XW~o6!?upx89eN#mjXT*Ldct6kB99k zuM`g}x)BRU@vcS1*_?$)yb?^XMIL72d%6Qt<_Rm4_;bV05C$du%+~zu*-4;fkKo~k zV)C>5a23IBCy$?%fS(Oi{EPv(pcs+QKqVgQkjl>v0ZV9&pDn{_M=p~fYkqbrE}QeS z67aKCz-{oe65(fD&4!}=Z~>&&Ddnm=|Te=G^{ zVO1Q^+m3vKKN0?zT*`XlpOHU)aU2)I$?jmXM~*)&COZ;e<79v~7}&ZA&OYK*6)S!2 za)9I+0%>;1C6t2*_wdGlNL6J+UIR-NewswNmiX!0qrgw!Mj)|!15qT|_7${oCnt0V z>?<>N#J-ZrrPAEKf(=C8)9=~7@YF@=Uf zr`lI=d`rN-a_kqKTCuMj`H!G|OBSpAR5wPy4lS_&47 zqhkxpAeB8!v9jMFbLLE(K zc6_cg;{nmAisCqK^Z{AK-51b$lEnL1L)fxE)m9TG{4PpX!z2R|Efg9hFof0l%gct~ zP|5Yq*r^aYXY_-yzIWHMOl*x;))TfQ_(>yRj`Nd`J&9A^fEtl>lrGx@>q!|BVNco@ zyLpiEoT4Y+p#NFgh;k4i3nNPVp&(@`#*^W`%=sj~$zK9ZGq(!*L<8;!jhXo6U%-6A zg3gRTh|oD4@-fXPkILaWkUM|;F|in~6|tbaD{|+%#)2{u(&mV8?bu^8uuu3{%oFXD z$7Xm^r`}Hm{XYbJ*pm|p{3xfqG$Y%2;~_Atjc8oy|7&sO@ukx#7qG7>W`MaYlI+s4 z5zOG~uR3u{FZ6N9LL*S_0V!|A?g33KO%~;5mL~gV&w@P@{U>b&DsiNq?9w1Na0gkF z7{Ou9=%0IjX)=2+)o}&7onsK^sD#JmhFqPG!7FrALzn^tz5aBBU%*2lXJ$E!JHTIYpy^fH@ zSq4LVrow=_IoTv_mBa!Wy4loO4>*(-zGY3S0(KGyDlwa}wSogYe2f!URT3|%Y-dg8 z=ARU6&gu4$wUO@YN|XIBM44#@*wzK5k$1^NCzR~{G+dqrFFz+!;=hn5GvI2Th1c`QK2e^ZO8))#%@a<=r@pSb)IkHn!EoG zD-XQ4Ib9yeK6v}(fuS^`Y%j`^9-IUd0TC*ORFYtz)&^eO z6gWrbwns0R;Rq62jve3KSQU8>FVOCY>#^sF{S9A=_Z9JUqQ1^8L#Pny$5-&NRF4TK zvd6=$nVm8EN;^3SzSl#2dAPK0ZilkMmDMMQ6Z_~wJqfcf4sV}yp`oovDn( z_u=mt)qdUuuuYHd0^A4)YftR3eejek=pbqLQ znFuz7c!`Xe&@wa7jr<0`FDx(<76m>xf9dbhS+Ts+p35l=+0at+*lqk5Q7M7&N+0G& zfQO8lqh^y=+k~zSij_y7n zjERi8f=r~l!f|VX+F8Ei67@bI)093xUVgv2j|&)VN`O*JtH&>+{@&zRT7{~HM{e);!ZLibpIGT6LYZ;DUSXbACPgiU0V3X z)x8kwC~D!#Qosi|`NZEr0gx4-=6OMLV`H%62k~+mnzG|30FaE=x{v`|^MHS-5r&+` zj%BNcXvs_`C6?NWPSyBgyWs8iE^SfE3QLnYsLYh=ebVt8U-0-@vC{%15Ulv zh^#=$UPCwqfD;}ZzGz-q;hd{4kbX7(i1rOkg>G`5Ga&+r2gq@_^)XEo$h~r z1Bwy8HwWPB-T*aS-RC#Bqj2NH+vJDT_(S_dqFw&l()b$+bKtip=2TxqtT|s{%}GA} zq_JjB&F;@rpAF(p=BswyCrDrQ&v1Od+^dg3E!BresO-x8CBPn_k#prr@{jU%cHNfD z(K7(|04%&U0D1uX{IUqFr0)2wP4T&Nwj7EHy`2kXx25=`n^#oEl%ju<_h5=oFOC02 z)e(umBAq}aurn9IZ=4B*TbpF27dVgNX=!4dV<(2g5DcUE!brR^lKH8fxuEe>@pr9o z3eXXtKURoyEH5(=`TWnCj&|Xf)yEUNqcb?qg1voD^koNvviR%$N3tWUmE}9&!@_g=MF^| zE!sE3YI{h3+Yf_9zT)j3}Z zu5fOB1mYa{@8tf%SgPxE){buQp%r6G}Pjsq1}_a~YmuotGxgL^Rh| zk@$wv~B@gb!DgO(v3*WJmCzjN71gWh)j%+0w1IS2t zqrwmB=7Fd!Y-d)y>-yV-0`Qu=lSl3^YHGXn70GhvwK-|04uq{`&i#%;H`@9S?(?H zkq(?$9Y!KxLn02=aD{h5H_(gG$Jxm-I3XGQ8=Z(0HjN%3T^fcvQ=aRbcF5P&I)|9S zCim~FK>FCV3ROxA|6#?-St}~rRKkTWUVns&$|8R5EYLJ0E>2(V&evF*T);)8Sj~sY zjp!JFocZ<|d|R5>6T>r4hT#bzeFO3aae_&o8kaav>bf@BtkZQyq92WaLV?BxZ~bCD z@7(o2L$CjKvi?VZ+x4HqFVudF(nNU{Y{)NW36XkM`DEcA%MgsYDVMtr=o}hT$}HAk zA|p#dr0`(4oxEeIyo9XpEbsMAQ&Hl2(42YUx{uBZ*ENNz_s0KOFCr6X#>{N|XBC0( zo7{T0BKIl;zA=z*t>~MwH36PTNg8=F^gBA7@Zwbner1XM5djQ@b;CTq}8h3V5Sr(!xPGYl>NmM2lsmDhQNu*GQjHqY|S_(2lEsrnW z_$BJ8u%_2d#=B?(0;C36v429z3*ngeSQ&;SyEbq5@Q)7kZoY=3bME2 zGNLsa#^s7iCb(c5gt*aC{;5a$G@_DJocJ-3GL&?o?E8)rgwP z=yDu2k0Q9UtIst+twu1t0Vo3%2v7pj11bbuWPzqX5^~1na9u?k&_?uiIPlqu;G$at z*Oj2C+^R!^qsyyzM_Z*$JHp6EL5_etyGUjO5rQ5*fE9S=rAF6*YxKONUeNQD@=`x2?W;f2|pH9fB=jwTy#L6wl7 zL0{#3a!?g-#+a9aHOo*1?GMJC3{?hc#R_om{s{j~**g9-#HHI0_1W0pTztcZ|Ug za36mLnlS0!;s_UjD*OrM`~~tMW(dP4iz8i%EfN2}p*D|_78!hYAEWd8Fmkt6>L)pi z_C~+_`iT$0`fq?A-v4@3O8rCdj`hdZrUSr=o&{X7dtf6kiTz+9@@1g-K9w&emA?f5 zCDR*x#^eqjsgGz^y9L56C8 zH7R8<2jZPt|3PqdQY~O{Dq26WUVW zRi}Ylv2!?A9_VpkGx0U>KBay&d8+=FA@KCTj`&(bU@+?T`2}sNvLK)0=uDHyDC`#C zLH-71lerI>R^toR^&gm@)pS)pEzzTuvBGn8!V+{Lu=x-0E0ChGUH4T zohJ8O+=eCtEzH6M+c6!dtowmG6iAs?;sa9xg~4Obki|k&a(kts{3pR#QBZp~bykF? z8zA^NI}sT>f;@8ozH)C4ekvopRM)TOv=&)p406I?4TaKS=WML4cCWehHj z(sCIeLumf$54ivM9ccipSG#LJT}zmFY{9LmrKA(ux`thmhqkRm&-BUbp7QL z!tFciFP$;*Y4Sr0`pexQM?sn32_*)5GC@#(;W0Jap}&-(N{`=t2mOU7-gxrf_o~0t zrsyxqBEm&sbQjn)g1XC{p6)Ug_(|O*k8A)3twDF`H(cv3)p$qUr65Ij0Tqbj#r9Bv zdw{z>6}XCbt!gi$ThdmXL!j~Wm8mG`>nj7@i6T7} zan-e^Pl&!U6Xkq;B3)m>;dyv#=ys~Uq7Gncqkhp>&Jnr9nEcZ9m8i%|rv6lYWjNk# zOJAuVl|Oe|`pUU&>MMgVM(Qg$sy%9%1RT#76QaJ^_3ne`pO00T3_kVJ0PvM zps$oS(^rboN2RaaIoQ`%dJ0OV@YUYitFH`9*H^}A@VBh5oVRoJm3C(ZAaF^``pO)R z&dfIT6&nwBKwr^f`IiE$HuaUf-Ic!bJmvUQePy7bOwII_Q9w%SD}fSe`bxRb1<_af z{X6xQsY+iNNPXo6KtluOnl|;7JC9NN%E-rlNPT70&vu%=vdh2nzv^G%rJ!GfpE2p&RUm4hje`ROIgF)lX$hd3cR^}HtC+JieV8z~r1yh_VeN&^sT8IRz zg6&#Gf+2MT;zSq;mZX2hI8~796FETORXGRtR2G3pWft4+J{P$t{!+Niy0^uEFc*e( z-(>X??^?B~-q(_6N54v(i53S=m-V*qH71tPFZiDJ;lFvklM6 z?FQU8;X*tsAsM_1Mgg#34vJ@|`BcWCY;%{&z@STI7K*g)Qkk($m&*CQjmok##icSz zxl{(W>{4MgJX^J*pQtN@gssDsgoLe9kGzmDWPEStQW-^nwct|u5zX|T%~tZRw2Be? zVaC&4Dm?`os(<8%ggs0{n~C2Fnvy?Z47Gx1=L8vA(* z@nFqk!P5LG(>#Aly|^h-V!_(?)w)WQ69s>Y9}8v(z@2!ry#!zyu-6C4^}K6!x+k>c zR2huMGy$M@GMxZONLUA%ty*AZhVrdc6s=Ta9}g{!8(%+zTUP+x``Xd8LmS{*?h4F5?FS{*@b1(D$zlaIZ7?u?_zU z^B(()BHh2Tpbh`Z!+5vtc(4fi<=3CCuUsScUE`8S^{-rrcRz%GcErCDZth<>G1b4anyng(dwM+BVLX0p`{KdqjpRhBc&0kK;oEZN118^tbZ+AS{1Ni}Iy#;+`V2XbQ>sSSZNjz9Vv9GTjFDMmU;chWH zZo-!fFl}p_Gv9QNflzIy?jr^Kd8jz3?Yr4N0HGJgsxM%%NV?P5-%uLfx}c8-DNG} z`>>bAi?>GPO!y@U4r?D7vXyaR_2{bF$1)99{4=?wDTq|C$dh=;QPtzuiam~Iz;>%L z>{-Mo{M}$PoSJ{NwpBFO9)dlg%eaHbR=YyW?TzatN+hV^Tw=uzgTNpHPzLtugp+3? zYHUOPT5fYGt!rYu7@~q7feab0-HJ^vuj8ty;4*GQ!Z@6wgHoLq<&7&4Q70`|pP#T&a;vJd(p;}R%2IGW=$t>hrEU}$cEN|Btt>xICU zv|}C7TFKMo=7+waPDo+g|u3w!Qon5}bg1 zAufus*C_fj1bsD~XeX4(LZ71=n|q)6SIbP9cvXN`dLzt3_*TcuhvN(Ucru6kVXCP7 zTsorW|F|FK9hAhKU0oS~do?md8<^WnHw%~(&_TF+N#B`41Tn+?PcvE&t{W4#Rv}va97OG&@^ln86 z(Vq-hHcmD1)X~lonh?8fm>>2IRq0 zk17Cf*(x*K4>#kaFA?>KlYXB}k=iBmCNeuX{|am6XcFI~C`X!vaCgnOA|b?Z9}O5k z*(Mr1>gz};B;;JQt4TP63W!om;Vd5&P7-wHI-@qBFs@ij{qd z){nS=Wy4G~w^qtMs}5zuo@!2?-ZoQ_x@~69J(ZS}O}LD2epCw<5aQ1cL^s@P@gMY~ z0oX6&%6^&iAtZ$B)?&ZRR_vGg4VzEfFH?jP=|d&8%y9PrE{J|4xZ#$dpwW-2b1!kP z0cyxSOcXx!N02Uw0|5&pT(AIXgazROi=;o`4*+q%5~RPtip@^ZlJ-Y|AXdBg;wyOqb^EF%r!0GxwyVw3mt3f%ak_qyq?ejY_$nW;of%Fdd(E ziQZ46_yAWQ`OBZrjDJznciPeRI;^=p>*Dm6G+t^sBjyBYGH z-@lZ>5_Udtv7a?%q>Ix>IuEad^zF?mrdLI>yo}9h817_^V!kfRh5T|qVCN#s zK?*!56dS0d)4!)=IHtZga3}SJU&K^FvYN~K?n$eUhdMz^$zc(b?{tlGB66d$m3lmN z50KSRi%YCx$CeE^5YMb)Y=_pgA>Peh4@iiA-36HTREpvL8Qt>uLqOlM z1^5Bbz9);z1AUCZGWrw|?-F;6z|Si&k79Xna@M8R@Jz4zxqB`lx!$$e~0J~A_*&sUny8$APn*}hK?K#W%)&xaTbaLp5D=X1EQzZ3jr|s1}3r-NLa9F4iPPXd1)L;wYX6v zzPNPbXP{xvkPAWw5Q)l)?G5TJ;#MWJF|OYN?)_RDj=!cnP6+SES!`M@yS<}P%U;A4 z)iT?f@-!}uTJ|8WDN`YUeOvSGXS;(of1QZHH% zdqax}3I2ehT!7w&9#Oz94=P~)K#?{Tu&1_D0ed~qC}7>uFJA%c4R(d4etF9ZSQl_D zrGT|pS5Uxq!<8st>oD-30!D2-eP_>b0*t0J@7w8Ltly_gyMZjo=v&dhrl7Q-B+j)n`3hj3iR6ypVu6i?i$&{Tm8m+|0#66~ zJ?6zrS75`Y^QKdIqSnGDaL%BGVa4fm1)5^q!57<4F;)P{eN1?mcWs$*uxp_OOt7MQ z{}0VLYX46v(GVSM85p@s16NiArrll;1=GICnleM5N4eZ8m zI!S%4B`#1JSXcDMCFTYdu#pfedi)c3jF}t=Q_6k)Dt~()oBDq;KRr z-t;N`s~n95WXWyoU-xhV0upz+{uMz?fLn!HwY;NbRx^rpeaQww`}B&=V4+tK~TN@~MOS0lMY!gXf7P&Rbis=7Y(%7hqGf@gpqil`SnV z>eZFM3w!0@aGqY>>1k@ANy%_0U;~gn zPf6Szjhk?MLpoidv`t;_Hq_d}>wLI!P{6G*jh4HSR86l}a{!){Iz>;UchA#NhNWqG z51^#`E{hvYQUKbJR)Jd!l@1I8dfq#LtJd=_P1Ey;(;_H{p7%TtL_s=+TVNevs=Nb< z>|Va|#wNa<?e8uXX;GQm8o=Dgg z98&|jRRIx=?5toO_CDj61;=0{eQ+M_H4f*C0%UR!QioxWIg#gsTajWhac);T(Kj1; zBc1A`Qp_$w^Eh@?DyZ2zRJ%13qME9sNCC)-^}q$}=scEgt2CyvNck$+CV*AD#*Q{0 zF%Z*Eo;gb3OQ{7@fIJ*bl!vR3HT_Ix_u8%CB6uu*157yOBA~=K5Sy*puk&b&JPOT7 zoxH-y@2i%96JhiQ}@CTG4tul={LiWXniQxXr!-4e+ zSuNI;0}+OEIgAsOuC7qNj@{97Pag1zj&79sN;f;jhZ}7?28x`&O1cG9>zkpAVJQj4 za9`*9z#jco=$#VsDwGrPM%MQn>I+5J#^+hlUrB*;b6n2i)VeP-0SA>(5B`mnc!v8d zWYk;006k{Zd((=R;2U%y{6ZGs;=YeUMn6dQTPnUF>P^Xh_ttKKCrC24HHK@y>Nq;v zWKMe#?0(Bom2$Z=;P;Q&*1NQ0Qz<{OE^HG3fTaoik z6OLfMkzfs!e;~d53^c;>53BN9!KJIF5}%`MP?Y$tvhCdkEW%(_z6D{Au=fKM4D}+Z zitQnY5Ar313k!>qXGN3NwBxvUhvUDgsOKOyWZ*?I3A21X`Yz+AYCz$^y!Y5K4*o+K z%43y)c}C-{3O<4N2eJ!zU(WYRp4tMU52~FmoCWt0WS*I z?#0rDB9QsGBkqO1kb#tYfqH2X+zWI?R!t=UhaVuJIZ9ON4M>QnG71+ERUouL$?-&$LAWO0>}E~)B`(QQ$UB)`N((E; zwIQrL-ioktH9$fd5END}MK?XpNoAg#lg$g~1d<(Zgq6u8FF|4DU492F zIfX2>9WH{hOAZ%BO^%jbJ|K%slU*7UzKB z^D+lodKnL@;?ZA`U!K7eCBHClE9n=B+#=FZ5j=d*0n*2R8~G(f`K6+IJLDJI^8P#J zmo=c99z1=blp?>xx*8gCNeY$i8IWIuqPWlS{Wj&7FHzt3D!;sdZnP!8Ov8IEzZ{>T zeE3FwIWE(E5^oiMCgD0B^2>29Rz`|E(oBA-(DKWH7#mnAyrL=Po5?R!*~xz0A4z$K zd^S~nq51tSX9a{`$}jh#Le1a5ll(#!?9F#e@=M+KA;0W}R#WAdTP8q$c^Hqi%<@mb z!^kgv)8&_*ZOAXv5Klqvy=D33y6u) zLivTaU>(P0VE&k?Nzlpt39gj?cZE}*&h4Kz*_?H%WkIHZ)aa=_p227<-gs&GH>7J z?D9LDv{;Cz`nBVM?a8#rF@^}ApcLaugtu3JLHr9ZTrfHmXS|(j(uf7 z_V+BG{2T2n*8o;vmD_D!DOKfLwXYPyy!XFoUs-?6PO`6L?bVii^1o(Z`PncfpIkin z`;kwsUDbko@{w3JeE(03N@u!#r2$4hWncNw_;B^bUKRW8Q`j4JT|48fK0|D20!Y%aeHZA`cT*R-h2^0G_3D%mCA2I}30 zW#xfZEGq{C80nUkU!t#`R^Nj?f>wVHevM_ta|2zEF{W5n-l4HB;0C(17=nsOv4`@X@vA5bRfPq5)QJEop!+^`^H0USk_LSbu?J1${v!{qJ=qRfddx`=7 z$1rZ}0Qe6i^ja{g+_PJA@V||+9{6w4MwLIL8C532a6)}p>?tpL_7qxH4B!I$`F*(U zkVB~RknIpyCX4gPgs+6W^1N?P`2g@R z_LTFa3q`H@gRs9}IfTR?RMD|5c_qf-Zl^!!R&*gS;uh>F>jUf2NLX1F8M>?s9ePq}uml2@Ka4w`26 zlsjQT4#cZSOef-1R@1PYyeW4gf3a`6rK1qNkd;bj5dznW64$Aa6?GJ%6+IC$2#-TF zAuG#C(jl@`Mv~RsLkqw0dZ$Z!BeI+WS1q6X8BbahSs2erk!6MvS!RpKvK~e#r+sZe zWSQR1ke`F47fNJl<`RnLPQm~KQ7h%nqZLMKQJeCt*tOv9-~xSH-CkZgxa&r(&9*A$zPNOQoO5~d)E|h%3I8-Cw@U(ykl4rqT zF_xFs6GM4~0mBiv#XKShlaw|)Pcazg@a$jp)ZpI+(= z@kB(VAJJ0*S%*775zxVLq;Cz+!YdvB!r(`#geO7LhlB|L$R*~5sRYWo# zhja29E%ZDtLXVmg?M%Y?jfe`zX5FVfnP-}oc?iJ39$g=30hxynbr1|(%+KboCb)DE z4A!s;g24`+PR_pPE15@1jGs@!k`e@?{dp1#69Q+__cSN(Ks?Ozm=`Y|2Ju)D4>K1J zr9Lf+zyYcf3%42%BOtb0i-+-!jbuE`@Fo)vLs7uvhdxCM$Up0yUwpGwsbW$3@=2vJ zOe~Y2{G?>CE{V$JAXud-(LP9U;^KHMbrJY4&kyY59)bemZ}Q_|<^q*epbrKH3KBg>*IUVQ>0dHu$N0p+!T` z8v%JJ6fB`e$P*ome`zR&n7YUHbhKq6W|;Fyi$otl8p=i0=2itB@53>Re3XGJRsF>Q z`RFrL7F>C5d$z~zo|^1~VU~-CbnXBpBHbLJh?Me|7C*CdV9aLnQin8o>3(v9 zbb0BW&6=*nvbDUF*dmjc65@N24xzah1d;L*Gf0YwX9_Mb<5(Te>sJTlrHQ86=*Qso zt(th6$xF5~wr{$;bQWr$NVKSdGKH3m3LqJ&oI80zv(I2Fb}6vY2t{AvQ_d)iK3XUm z90>Y78vqfZXbdhO6b-hfT!AYi6kUvKGT=OG!Z2P^D55+h8$7p1B$`7Bp#}Ea9l%K! ziQrB0*{{w1KqR^dzX6eGaB4hFsz~%fRjNo-4I>f{islh>t&2ow16U%>*n}EIqJ!CK zEe(O03&?RBSih&BsK0*Y>-+gp>#`7z`VGiJzXi^mLRlyi7eQG_3_!VC5j4-Ee%~hz zPLqW&6}10+`>FT|A=$!I{C;EesrU$GJxM5u<~S7(2uDbfgsufKQv4$JJT6r{%@d?h zKKMw9dFz7@vd~~1P{W7Yl!a`{LY>cUTNV-_h`Wc-pPeZGJc$XC`o3TJhp7fUe&|!E zRQU&+7}U8I6)5df;fn(CG%6nWLB8L%{Db?@O0|9+lTc zTK*aA{&YL!AD+ZT_PgW@k$)y_Oa7sVCi0Ie{~r0Wl7Idmd+#0?MRoR%CtNfrGV#{Z z*4n6P8@vWvH7U04$_8g~gHZvoBBr8SeRng$?LZ-_qTD02NDpp%*wbix=h-kwd zyaL`pY7L5YhJd10z-xY=&vVY1o!x8#DBAD){>mR@XXad=^PJ~?p0f>@NeWe`w!jyWEg zF)CNM`@c*25Ox;f3USi!wIGACzgRLDr)#_4#BH;U*OiHuj&npKP~4g|iF&)796G^D zEyKWSlE^-FJGe5SmFd6YcwCR3LXoNwU|g5td(~Zyc#QX~x@!{tTB%#{KkkN3Y%UI! zEwz$-Irn`0C-N|`41;wahIffp6)?K_?#Rs?v6VU*eemPHm_gNm%XmV8-h!$DW9A-# zzgF@c{ErcYolh``$=84*TeF^Lv+u$XC$}rJ`ru&OyQ{XPD$m+L)$Z?ttHuYC$EP?~ zncwyJ!2@68Lolq+6nqzLd#b`v984Kh%A{g_&ZH>O(2EPgp(#rUCxUY<^YmG?lU;p| zeFqbcINKs6EmmqW zxa-^_b%FQJCRtjq6~=A&M0!eCEPF=?U=3bPm3gkf8rzuxS6H)#mSH`KGJ&H?a(ZS2 zjw!u_mSusr;s@zpCB&2P9G;MKr~F&XCEp@#JPZwBkeND^Smln!NT{ZskXY;=0ke|N zvY?*_kkO2kK%n2q5imQ)NHChBkjmZQADTH_4piWD7N*g6ZMLOvs>AfqRL0Jv3QR;ferqo(979N7RAsNMb<4RCfLuDbicZ~-Iq+b{}3zXiI~ zlzc*rQ7QU~H&}a9{u-3`*I*1DL_x$Z-ulf680+>?7d0m=Kyob$!TqtNcIR@|9 zau1O@mjJ~6cKHSx<$NYy6xc35+@!Y40T?_#uE(=AYY_Jfrd%~Y{x$5F_};{r!vPN; zpJZ_dU~#*mV$}dU{%YSkb-v?A=hl#$;E%p=)!d@E06+?`ux%8H~43)VI zD#OXjB*`)6Vdm zTl{IPlvHz%!66SU7fk{`!bGzCZ*C$+sqmsrO z?(oL5O>H0YqfuYMBKhZo$m&k;p5ZHV;1_zVE>Vv$6nQv1qU}4(uVtmS;MuoFv`%c- z{(c;jInSdQD6{0t3K8QXs1J;@lH9>n3lz>-vsMY({SZl$h{tzAxAb|!DH{aIlM^r; zB>Qw0VORD8PmZQ{LuRzTtI$L&9dg~tXBaY_xCW;|Q&m_F?_#cr+P{Kvw)%E7zcD;m zjX&(X;Jobh0ICETgH*6myRqyc_>UvI0fW&?b~QJv%4;PDgB0JtJfisiANmEX9H-lf zjrFzZQ%h>oJ#0r3$V|s<>~xp+!`5XbF~zvCM`Adb6^enkgH?diQqQF^#OlLE#+wSh znaZkoD1+6%t*+j7R@;e1^=B>2npD2b{z|{%{xI*abbiCW;{LGGh35zCh-LPFw^vaS zkpynPr(%#bhz>pyq(c?_)*}Y2n2rxIihT;3s#h2kvHA?BSO|0y>>@6O8hW`iU^(); zZbRw7CVIYD{a1eO{!3QwQ2+Bg@4xJG_g}GQhx-3Z=lxfG z?*50Z+oAsN>Ae5Y=kC8`L&yCOIb-PQw#xHh4uvECpY1#sO%DSN6*?+to*vWN@e=DE zNh~jM24RaZ(H)5CcpG%Io;a^4s*-K7dsYkSjFWT@FCV4}Emftfy|y-y-5*5>ZQz6c zWgjHB&y07*pz+;pXPeyv=k%wQw@5qZl-SN50U6$>zG(8@b zp8-GE66#$X(~0D5q`OnpeV%Oe7@`CkQQ1n(WfK%lVsNKE$OkAH6=;u>#GK)T>*ItZAI9taucOpB*@A3Fa0)w%#Mq*pBHMv=g zeA6T$T~n9FgCWeT@`Qs>Bc}#Kb8Tlx31Gpww$o>{qH1TRNV`g2Qlirn9G2Vq7ih4F z@u8w965Ao~Lt=boQP`Tc6sOCbH8o`)TB*^Pt55eRzV(9WnowYBi(kP{jp$k=yQkGB zs#Y-G{uVVM0g5aVx1!Yzp@tqd6Bfi#44RO zhz>_P^kOO(QccxLoO|bBj=_J_XR{@*@JkY&9y31!-U71Ktg#ILQT|h7vNO}l8&Qsk zNR(evB-qS-r1W?9_1BcJiPIriVYTQmM|->DIV+z-T8#||d*CCs zhwOpyTucx|qyg`|+b~jTZ*ci3dJ@@2(KKFLsT)`*kU}Z^C|mHZz-tA6U!O1~%^;W} z>PQZZ_(1hHJNe?i&E9+br1uw~gbfzoLjme_w2iaM#m(Ncfig?F6t{YMw|FlB7$W56 z(N`{gG+jaF5X(HMqD{p%U&GFqvarTiazy zFjG5APcuSFucc+cm6nCqPLNOK0-6x*6JElFNBwdthZnz*F-j=CrOEo*E zZo3YX+6)3tVCE5UU{+13-LIzixS`gp-mOW{gGhO4-?k{SqxQb4F`DjH9&^^U)rp2U ztvOV+6p>3IacXqdml-A z>|iZnOh*Yy7U?_1h!=HUV4R+vnW;D%kcoH=!K3eTgD6NR6$PMju6_|NiU7X8tK5}+ zecXZ}R&7pqBdn{$mPR&)W#+CVj@2uD4>(pw+^Qs6R1k^kG-sxf7h=hHz6-I1&8$iN zFb4CHVB2&^F51edIsjBg8@%}?Lfg3hG7EG}bd5UiA{P?J#-(=0Rspp@4;%S#FOEfU zU}HgMnC->A1uw{@xT=dAx+eDQ(rzB6dB{iU?gyVs^g2VKS49xL`cQhBxM(`MQRuZW zyS_R2@v?+IIq$h&I|ldxxJxX7T8H;!E%(9)M!eM=u667$aXpJG3#{h>1HQcVT=7;X z>$xz!-Su2?Twpz)Uz4|<3-DrR*7LObUALYCe$c^s{um!5Zv;|`3w_u6r@8@$5;L_7 zWhQ|dEJhTFxx_@Guvka3!hTk&X90ybB+Ik^Lc$Qxa`WsZEKTg(RsFs7=vUj{aSOiM z{w{d(tL^XpRbOp?7p(2DzdZS44zMQwzA)-~bncPjlDyu8G93kZ`!|KvyRRd`X#`p= zsGiA7NH+gz$>Vgm%qhixRs>!p6LfhHvbIO8+41u6ql7Ug-(_ZRd{!ZMQ=w~&f>D_9 zt6vv2MJ@_E$7G@3LPsd_eSIpY&3488#)Hwn&Oo^J128Cr{3e!R)Z#`QZl#daMg`ZD zVnWtzm2V#L6bR4ed9~pxcP6eUUSdu5x?}X1)=}1^tKp^@73mi*A8sY`@3L zcm=Rl@-D(SwH!qOcPlv`j|Rw0)?;qiGmIC`&)94C-cO0R0*c$pWq!Reiu&$x&=c0c>_+8v0~z zT%2Fq%xszG#P`6@zukt{4I>##hu~6_t{!K{&ooOHFUz)l3>xFIalvFDqqq__oVH`_ z%wXU?TuN2XQZ<7(*2dA060}9!K3KE@f~(!!*kg@Ku<9;wY*`vG(|(f z)2hDIijNaGXP0&Fz_MCNj+!7qCx`@qriSQ=rEr&M{XRFJ{Th5nbR8`iAY2k1{j!y$ z%nN|1JH<9i69x`e;u>rlJ=6GUKEh$`zKgMYKf{07vVR6t0*JL4RTW}+KSep^?}7tr zEcz?*=#GN^YyxP-2-Nz!u)1n&6h_k`%Z> zVi8tB!VMq84{;l-Gb4tYVE%xev0kCMB9Ec|SV@M&C`8fM z!X|Ggm@K*ZDXj5PrzD0aWy_gsy186dQ|aF089jChhlJc zCc$lv2ncW?t!I&rCe$@^2-ECe#-(skNaM+?EYB42T20g%kWLtk6!CS$h7meN{3v}z z*L2j96!F|}5@duF@jfo(r-;u2_<7XJV*g4~0Fb`9van=Wvax-BRYw-1$hWMal{^8o z6e}e(1Gk8wHA6Sv#P9q`c&Ed)3Qo&Q2KV2s8(;_Q5J|D*} zAR75|?u|JIaBQY94{`+W1eBvaFo++AP!2K-?r4BBiysY>JW{5q9f=X1q8NhxHi-nK zJhZ+Xr}k;X0VTB= z&0b8tghspr@gG0hVzhEUo7t_%yB%4;1NF^zgJsK1PA`8m5yoHs6Sf&xTd8-Du-hxf zi>cDT?Wqt$>?i%404n6s(~~ij06ird5BC<}rAZNb4pu^?2&FW`6QR6!jSTs!r|CN9 z1!PN9Qq5edv_AmDV7%c5BN>yVcUE7>shhk<)s#6tL!Z41FvMKy?(IjC>=$6ZEP;d& zt51;RJ;3#>)KM6m*T#(MkdnuOmtRYu^aPdAFnCe+0L`7=459T{h5{@9>sf)7?~Upv z#r!>fT6xaf=5Qs{f=@G;Fm=eF%qMvh{^cG z+t{LC_!iE~@9m#5d?K2+Ea$_|va?;3o$bf|;5gJ{Q6q14EGwx0YMZdn_F;wK)ds?yHJ1n?d{Z`u2lNdzmV7cvyhlcJJO%_R{GN=qCWvY zlj|s2Zcl$2Z1g8}(X!E>DpfXAUcZG6j9@xteo*%;Q)uKr+_OBLe-*1i5k479A@WV2 ze~1Hv`>p~p{2Mi=L`!jZtorq6GPd;tTq}bgww1cnC{q7M4JuNVK!Ve_7?I4_N}_Wm zZta>4vEwdqIZz+mraWmJc;l#=>JO~Mh4^I-b)1dgk?L!i$^AC6Yv&^X0Otu_>@&Z5 zWh8TasddZzl34j^VTetHjXLl5eKJp<27ib)M4EjRV=bsj^%aFr6bnt7#u3&&XO0e++F!@hfd&=I)u4)hwLJ|glB+*GAQ943e|n&u{)8)- z3+hi68slQwo#;>7790KPl0y2^U|?ESf4VrUKk*7)tv@|bSbuuJ*Pk9x`qMk;!01m~ zwEpysU7$am|JCbH{<2bkGRyk8^rtO5qd#4Y{j#I_Q%31e2C-UDRUy_F(w~3U3p<`v2?KK+G(B}b3?1L_3gAC>$T&ex_s7Vc_f-S4xG z806G-L=ES*9y3brPgc6mg(!{7ytMA~Z%A|0eTL#Kse7@AXmXw@OL^C0yuLhpBp!Tu z*0qweb?=H~$0?`<@G|wH$RkF0{01N#AUVfU))SKB{R!m+PWzyU)|d>tAtS4z?I|Se z9#63O<>*Yr4PR$ci)PluN|O2TTCHKkfzvy>R*Mm~6gE%q>{_iXZ+e#a;q#_vP?_Xz zuFslsb*6spbf$g)e3mo)2CW&+v<|=XIg?1C(3wd3_7(|BATqeT5~@)}eE+FJYeipr z4;asvN?)qV)tAVD1Nst#;nOI!rj8~@^<|Rl@T5kAi;d!EDE}##vbu&x#N2E1*ZE?+ z$ijnY6%?G#z+?B)&8%F@8U8)a#+_e*q{M|zn1x4>;tvL&f3wRtb(%-2^5uHuIG zpDsi*dD_GhzVdOcC#I}$O{|1g>ICXb76+-O{I9IS@VHiw3N7f zqs88ZIir(nwNy7tePzVk4+Av1(JeW;(Lin!%&i_97O;>u=IJS2H9+>RkWzH}wnmwi5sKH!3V6?8IHX(!W%G$Sh~({;2#ijyghU&hJA`}kn_LMWSc17mhzGkTQ$ikk`6 zs0DB|bqQB5v+ky^>8x;$ujxB9JNov#chMA0YTDwZqV`~H&z2a5;rbRXZP=MXrw0r> z%88PEiVhXA#9GNq(0sm8FW_!k?z@{p=vmxN$8iWS2h%_tNNJvp+1p`%Q(na0%}9F2 zhC7bfi{$E0d&MT>hrxw7vigHT8TM!7G;BQ)vMG!wqGk(C#au)nejR8_39twS}M3$C#5219x<{f?vP@;;>Xi+){ zy2^LsV>%j73Rjem$x2G_x$n6DCk@a+FVGLgplQ57_h8-uLNFbr{fIpfS&1o_v^T0` z&+ouvrDOMc;eFPBwMgqyAH%Lo#!>7sWon-PYA)vQ`xiEY)CT=mqfp*s4jzG86^54e zJ_#S{Bw}v^b^!?lyic#;r$p@Kd!Lx_o8G6Kh`sakBle!gMm7<9eMp_@f9yD7ZzaBF z@&7ea0dlLkTs2#9D+c5Glw){1|I^6;oS__6a=ZSgp@6UUKNX9qIuNs$>wnVmdfNXK z3i_Wkb51-YqX%_~zyWoZ;MY+cxbGC0fG34tbU+Q2^Q%E(WF?seG6%yhBog`a{T}8U zz%XKorU+q_%lALwa8MRMP9#ZJ3@%a?^gneZOHhuWJmH-!O(!uqE>58+t_|)BQv*^p zlNamw-+(`g`2p#Vs_4KUrA`uHIx)33lXUlZxCl%Ds3Z7}$FV$b5*!Eq`DnBGj?PY2 z0B$97T;k(9(`rjodA8MKZ<9j;g1qOltk~r zHKha_%%aNs0i8Vv-^)CGRI3ra$Pb@nn2dBXWFH$8sm3Ts_fH#qXVt(JLR-O>EC^ zB*|IHB-qyq8N5XA&CU5R1cTLcU6P&aCVyzGT!9(|7DxH0&f+x~`>}k7(R-gWsOKRiiF14J2*FE)1?!cl>t z7npFq^(sLk{Lkt@ln&^N1_Jt~W5_C?Zw7wx9|HOqEi&f{FCZnzkRWX?57iAk@rQm#?O?iYa{@qSo7;7uemIvee`$eP@Z z@)ahJXuri^ZcxNFT8YQ-%P3+G;diV$US4J0z_L6&B(C&f_U%#Yp_lAF^X=-DvCIz{ z^@m((;U3RXplRgr81FxcJlH4Yr}2r*?NK1MVQxAjuwUp?8L_A+vKC0F%w?gIU1fN{ z87i!uS()q5W-NVQIRVVBr{k}ZWOxW%GWMz9KL<16tqT+Jm*gl!R9lMD>ahxs%Ao}) z#`k0izLMip3ipVof}iG0wo~X3)n{XrN+u;IF5*Jv9FQM1HR#W}K08?}`As~Cu7i{s zRXENfDH}92!}A1WP|1!3GYnvD!}qfA9fAh%wM?V)Zfq~&sKU8{fGrFJyg&j0@0CEn zi+Pahx5W5J^+-GZv%o6G1Lw3B1$46~IHFNVLeoSLniL8+QH3NiAG-hgjnU9P)fyVN zi}xHTlWZbB>GM=0p`Y1+wRDZcQ0nKsDjx7Bc#`u}-_x&s{p?;9511-p2*{_UICajO z#MFVRg?Pyx!jG5A`UX%^iMNV{jvFcH_?W;}$yzEWcowE_1M9WGsnIcpfdtSoKHQ)p z&t7D8wKqZ5v-}mCMvcKgVWkiDLuu)szaKNTvcq&#wP|x$AEwr=XaxzwtJD zQ0URpc@kfM!dr_0pFMagzhoN{BccQV#ehM)_D@_fp-kURiXwYYp^P8@62#XP*lk9Y z8y3XXZUCWo3K0VGg^^FT0CoX<>`!82@X?E+u$DK}{^D3YlQD2CpOiv(H|UD9)H@Di zlJZ0oNa?48e|fXW!~&^D7~f>50Z-^?f*|=IJ^)OjnMgS}2I=u=qxqdbrP91IYp{GNkq z9jicvyB^~Z1%>yF$RVtGAW4eqqZiU8EBPi`X?KZEmPUDJ$O1RAMd9aJ{8A>9h$faI zk>4#`-|)3(@ucufrB-UVr1gX_wyjpmy5KV(?^Y+Oj)eG~fQRdC2s0{vQJ<&U59MS% z5Zaqv^1ZVN0<#e2rI933)nX+F%Qymi@l>p79y+Ja7OBag(1mC(fI<|FvnX^l$23)7 z$=ex&hqJb{SM^4lK*M(cG>t^-@iVo_V2nW*EBQB!oSNR~P}3#lqXb2w$g`|fJ%uC) zmS4xULZIO&1R4TQOnCl-8apbmqs+b+cKD(!mzmr9BqX_!hMFk&JBV4Qa;eW4(C7g5 z$?AI)Q{F}U`IGVfO)VhOZzN1#>qIWp`X1vA%W~ok7vM8r7C4`!WPrp%nSffs8YHKo zJ>f8HWJAHMDD5#n9TBifR0sK>GX8s}lY*&o&RCHqm&@QO|z3#qngVF+~V{YCS5F%gy z+sJstPY{oIId&=aKWZ~RJu=>}UPF2R2uG`poQGzSeC{$Fk?1%dusmf`g=q4Zr&nJagoPh zRSgbatf_wT!#yZgaG@ASiiMg?1va1>a&v&jX-C_-kxDgu6cNI2GruSik82tISn z&^r;7s@-|7Whi3w#d#3x0CW{<|GD+W2hQzCUz`VtS>?@uzSxpOx*t>eqDmNs?G1EJ zUWwva#QGy?l-3t3xGR;uc$v``aordb>O)vncOPnpTy*3j@~SE-gVWJ{$NC~NH|Fb$ zEs)2vH~Sc`oYoA7$qhg7EeS8QQZGTE5oUP{#?3=uRiYHGH%GvXcP>Ub4KsvN*h~&C z-Z>mu^p!#$n&!PeA?t<+%F1OjPzdwx^$~Rt$ut7|{wi{5pSktt)ArQC7cf?^3UvTQ zQ@8^OcX7_C<*<@ryfLun{e$$+QQR^cG~l1>fx#<{a1wq|BcuzG^r#b!aLdkUghNgov77_$4N5)1kncrSQ*p@2ciKV zc2;te^3rLTwLchkS>Rw;P#?s>u#WV>M|W5seCZ~m4{i$TgYSSkK_9$Kv^A{{(qE_b zLHg^oKB)b5`TAhH^I;BX1h-g@J~#(2MIW5^MfJgd0^xHdvK`b1yJMz`pOVdF<@LE- zNXWE1sSo<=Pkk`3{)P2Ho)I?uQ}n@43hRTPDbERUtKxUQp%wn~7Rk zhu>rBNvoF6gGnZ!TF&5GB|ANfpQ5*$e%=5qe0hFfJoxgwYb7ZydHn$&f!{?KQcwky z8)b32oC`~03`$yWRKRVRDKs78%_jV$3Yg0Lyl#MM zR^HdXzc)p;r4^b84PaEX%h#dxLGAyugZ@9o{`2&~t%7ae#)4#}qRU9#v_AMju0Ci; zn3d$AuY7!?Z6FJ&6O}$_&Y`|cS<7IW_dOKN&Myalj;A1I^uhiBcL0BsKKLvucPhbn zbc1n1briwN&<28dR1%L3?sKql7^>po(kj>^0{%ZCv>woLL6VZ&} zUpdOmz8IxH9;F$r1JHBI?8Xz|2#>I zW7a~={ROn8+=HcTHK_LO$r<`Xm?^WMAmT-GJ{*RX(37449MnHA_QyyM;e&W8xLW#R zv}su-P~@EH7;RP_{>Kod1kQY^pntFlg|)uNOuL&|7oNct{F{lDUrGO;f5lln672HD zFgx>0DFGs1=RP@@3f?&6^?-?6<@G=W7{pF-3W}1a>3aq|fl1)N!{@V|N^*%Zw@*q7 ztEpR2i?~zfzKM6ZDrxp)=A#=`y_z?2(u;CRe6LZ}chiYtr7j1Rf(1V#XFlj{M-DPY_EtESL4nn$9bUKg-Wdi3T2YKHgdSUCzU^b9JyrfXU z@ra@T2%4cs^U}E`R%#l_mO%O%z=gs@#U6zBLTl1B;31jQ#Xf!IWLya;mg!05=q}M3 z#^}0V)e}^fIS5m{*NCl*r?i*&Ij;6eihQ0y^b@aQWLlYzHy5L)GkV+UQ%aHC*iE-8 zQH|+L&0rhSU^6f3Wi%OsD-@P)^j5Uy_vjv0pV zZOSdVGKE|-XTHCdm8b})Ix9JxHOMd!3aa}D^oPN~@g7DX(9~DgKLTPe_gkjk!7p`R z>zm{VJ{T}R0ZJAOerzjWi91z>GgIwoeQ|xgxwt;W%UXZBmJ5_rB?{bnr@-?+qELkh z3*1-V4CR$jOFpCEhrPE@e%-D7h(l!skre$;>h0|qjqF_IMSKA;3~G&Xd;L>*DXnL3 zZ>^_@lYCNUX&}lD2=N@ZHhc$J>17T23@D9&IdXglV9u)#DI^i*f5JdH5^(~OwFyD6 zQgmN=MI_rmnL

-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�z-?%5h{ye@m+}JsfFizx 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 literal 0 HcmV?d00001