mirror of
https://github.com/red0124/ssp.git
synced 2025-04-20 18:47:57 +02:00
Compare commits
248 Commits
Author | SHA1 | Date | |
---|---|---|---|
53c5b779d4 | |||
107a122718 | |||
![]() |
55d0a4e598 | ||
![]() |
1b9a01f787 | ||
![]() |
f5b750dd93 | ||
7f53b585f9 | |||
67ef6651c1 | |||
fa4ec324de | |||
![]() |
f229de61d6 | ||
![]() |
df2beab6c3 | ||
![]() |
27bd60b5ce | ||
![]() |
c5b50f2b47 | ||
![]() |
d8dcce7f2a | ||
![]() |
126329608c | ||
ddaa446819 | |||
![]() |
8bad2d72ea | ||
899a6e6f5e | |||
0d3d8fa83e | |||
7bbe2879cd | |||
063d56fad9 | |||
df78865f04 | |||
852481d233 | |||
c516a6f826 | |||
b660310acf | |||
0a695cf09e | |||
f8e14b1fcf | |||
0ebbee1174 | |||
b3f3bdf8d1 | |||
f4a06d40e7 | |||
f2ff40a625 | |||
110ee840cc | |||
05f87bc78b | |||
88e711a5f7 | |||
383de57f9a | |||
c6f6ba9821 | |||
c5e491041d | |||
21b543ea4f | |||
8881649aca | |||
![]() |
a27fd121a1 | ||
c0d3087f85 | |||
273e8ad950 | |||
![]() |
f8fdb97151 | ||
c0ee100f99 | |||
09e628020d | |||
ea21b9ba04 | |||
230da6a3f2 | |||
3ea8adedfd | |||
57ba23c574 | |||
6516c6cc94 | |||
cbb0a1ad8e | |||
1798b4c6f3 | |||
90a116ac7b | |||
cbbe0acb25 | |||
b993eb8852 | |||
5e32d722e8 | |||
59f6591da3 | |||
9d96a7d47f | |||
d4fc2ee561 | |||
d422667477 | |||
aaa22046a5 | |||
775b8c93e2 | |||
e4fba8a918 | |||
11d57bd073 | |||
45b840a30a | |||
8bb773625b | |||
0466c7234c | |||
d21c387a33 | |||
d019edb2bf | |||
a2666816de | |||
417a03a8a4 | |||
baf4317ffa | |||
63a618957b | |||
dbaa8131e7 | |||
ef8cf30919 | |||
42d618ed64 | |||
8b07f7d6cb | |||
82f8ed12b4 | |||
e89e268280 | |||
aacf690640 | |||
f8c5757d99 | |||
ce03c371ae | |||
6c859959d6 | |||
4434db29f6 | |||
![]() |
fdd153b63e | ||
c05ab35a33 | |||
8d1637cad1 | |||
7530be1c44 | |||
7062888d72 | |||
f04ede3a49 | |||
4bedc32b63 | |||
7822351a0b | |||
a755f6c455 | |||
![]() |
4db88c0490 | ||
8b72deb1ed | |||
672b89b213 | |||
b9d2c2aad9 | |||
32cbfe1d17 | |||
848689451c | |||
b7e5dd28b8 | |||
e316558a7b | |||
1c6eacad30 | |||
5ac506d8f0 | |||
6e27f35209 | |||
49a3a20e68 | |||
1d0911ab3c | |||
7d3d02f11d | |||
17c21e260f | |||
2504c3574d | |||
0bd78120f3 | |||
ed8f4e3147 | |||
d4c9227830 | |||
f232c7d995 | |||
14ccb88664 | |||
![]() |
34833837ba | ||
5f27595ec1 | |||
65371d05f3 | |||
31b924736f | |||
ffca94d47d | |||
7ba66ff99d | |||
f1e127dd2b | |||
3e3eb1b61c | |||
b9d8eb860e | |||
4139b50cd9 | |||
8924ad12e5 | |||
254bd24bbd | |||
55d1bbcf86 | |||
3e3c922624 | |||
c0e6e56364 | |||
66f57ba66a | |||
d37ec12bb5 | |||
9d7441b178 | |||
dcf7e924ad | |||
41c4bf9d35 | |||
7b16f24d03 | |||
![]() |
db582709d4 | ||
deee577c1f | |||
535138d9b8 | |||
f106889ada | |||
e7045ce437 | |||
d86c8e9fe8 | |||
c0ef691889 | |||
80c189f9c5 | |||
07373ea043 | |||
236b5da9c2 | |||
a1f01ec5cb | |||
cd6c2df359 | |||
9afe24785b | |||
479cf4bbe7 | |||
6d1aa7c7a9 | |||
5fd6b561ed | |||
1176c42fba | |||
![]() |
e3c7db53f7 | ||
b913344722 | |||
7df96d95c7 | |||
6b4f456654 | |||
9c11f22d79 | |||
975ef2e4a5 | |||
2c7bc763a5 | |||
a152a8cb4a | |||
a378998e34 | |||
77a69fbd6d | |||
6a3ba48a2c | |||
911d81248b | |||
b6cef9577d | |||
48117c038a | |||
3c0a440fe9 | |||
974257e099 | |||
6a832ba11a | |||
0e184d07ce | |||
f17a3bdc59 | |||
952ff236ff | |||
7d44d503d9 | |||
2e1c4c97ec | |||
7c9ba953ad | |||
3170cb661c | |||
03870aa1ba | |||
2b132bc33a | |||
a7ea9e42e5 | |||
81484f737f | |||
634abdd38b | |||
c981ed6644 | |||
5173e7afbc | |||
5963df5ffd | |||
19538597e5 | |||
7656ba0a55 | |||
761445bd36 | |||
62e19df343 | |||
0858369c7d | |||
2fe2c391fe | |||
![]() |
57abdb3923 | ||
5f46259914 | |||
fbe19de70c | |||
27ef7f2e21 | |||
1d94989a4d | |||
84a7d46cbf | |||
9bf1dd6041 | |||
ed71d963e0 | |||
d9384f5d84 | |||
a7608d825e | |||
ba45fe1839 | |||
37f1fb534f | |||
e32905b59d | |||
d6cf9bd006 | |||
f28f000035 | |||
560bf815dc | |||
f58091cf02 | |||
a9903f7441 | |||
![]() |
78c75abec7 | ||
76091263f2 | |||
d99cba274e | |||
e70fc05646 | |||
21d1096edf | |||
72b74a3b64 | |||
b7f6009eb9 | |||
bdfd896861 | |||
d6bc8086b2 | |||
f762736da2 | |||
739077a8db | |||
3dcb2ce2ef | |||
e4d9e10ac3 | |||
a98742945b | |||
5d6c2c4af5 | |||
58857fff2d | |||
e6a7e09975 | |||
705ce422f0 | |||
75f5ee2a55 | |||
6baeb2d598 | |||
7b1f49d304 | |||
515ddad997 | |||
fd39b5eef2 | |||
![]() |
77185cb366 | ||
ad991d6a7d | |||
31cbd20f8f | |||
6dfc30d5c9 | |||
a6db4a7ad2 | |||
77631b8c0d | |||
7d0a5598a8 | |||
29c471e33a | |||
0ff86630f8 | |||
5ab41c0315 | |||
03f5b839fc | |||
9f4bcb03e1 | |||
567995aafb | |||
956156b412 | |||
a7a97b3ba8 | |||
41b89d1d3d | |||
f3225b8b16 | |||
eeac30651a |
7
.github/fuzz/makefile
vendored
Normal file
7
.github/fuzz/makefile
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
EXE=ssp_fuzz
|
||||||
|
|
||||||
|
all:
|
||||||
|
clang++ ${CXXFLAGS} ssp_fuzz.cpp -fsanitize=fuzzer -std=c++17 -o ${EXE}
|
||||||
|
|
||||||
|
run:
|
||||||
|
./${EXE} -max_total_time=900
|
81
.github/fuzz/ssp_fuzz.cpp
vendored
Normal file
81
.github/fuzz/ssp_fuzz.cpp
vendored
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
#include "../../ssp.hpp"
|
||||||
|
#include <filesystem>
|
||||||
|
#include <iostream>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
template <typename... Ts>
|
||||||
|
void test_ssp_file_mode(const uint8_t* data, size_t size,
|
||||||
|
std::string delim = ss::default_delimiter) {
|
||||||
|
std::string file_name = std::filesystem::temp_directory_path().append(
|
||||||
|
"ss_fuzzer" + std::to_string(getpid()) + ".csv");
|
||||||
|
FILE* file = std::fopen(file_name.c_str(), "wb");
|
||||||
|
if (!file) {
|
||||||
|
std::exit(1);
|
||||||
|
}
|
||||||
|
std::fwrite(data, size, 1, file);
|
||||||
|
std::fclose(file);
|
||||||
|
|
||||||
|
ss::parser<Ts...> p{file_name.c_str(), delim};
|
||||||
|
while (!p.eof()) {
|
||||||
|
try {
|
||||||
|
const auto& [s0, s1] =
|
||||||
|
p.template get_next<std::string, std::string>();
|
||||||
|
if (s0.size() == 10000) {
|
||||||
|
std::cout << s0.size() << std::endl;
|
||||||
|
}
|
||||||
|
} catch (ss::exception& e) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::remove(file_name.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... Ts>
|
||||||
|
void test_ssp_buffer_mode(const uint8_t* data, size_t size,
|
||||||
|
std::string delim = ss::default_delimiter) {
|
||||||
|
ss::parser<Ts...> p{(const char*)data, size, delim};
|
||||||
|
while (!p.eof()) {
|
||||||
|
try {
|
||||||
|
const auto& [s0, s1] =
|
||||||
|
p.template get_next<std::string, std::string>();
|
||||||
|
if (s0.size() == 10000) {
|
||||||
|
std::cout << s0.size() << std::endl;
|
||||||
|
}
|
||||||
|
} catch (ss::exception& e) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... Ts>
|
||||||
|
void test_ssp(const uint8_t* data, size_t size) {
|
||||||
|
test_ssp_file_mode<Ts...>(data, size);
|
||||||
|
test_ssp_file_mode<Ts..., ss::throw_on_error>(data, size);
|
||||||
|
|
||||||
|
test_ssp_file_mode<Ts...>(data, size, ":::");
|
||||||
|
test_ssp_file_mode<Ts..., ss::throw_on_error>(data, size, ":::");
|
||||||
|
|
||||||
|
test_ssp_buffer_mode<Ts...>(data, size);
|
||||||
|
test_ssp_buffer_mode<Ts..., ss::throw_on_error>(data, size);
|
||||||
|
|
||||||
|
test_ssp_buffer_mode<Ts...>(data, size, ":::");
|
||||||
|
test_ssp_buffer_mode<Ts..., ss::throw_on_error>(data, size, ":::");
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
||||||
|
using escape = ss::escape<'\\'>;
|
||||||
|
using quote = ss::quote<'"'>;
|
||||||
|
using trim = ss::trim<' ', '\t'>;
|
||||||
|
using multiline_r = ss::multiline_restricted<5>;
|
||||||
|
|
||||||
|
test_ssp<>(data, size);
|
||||||
|
test_ssp<escape>(data, size);
|
||||||
|
test_ssp<quote>(data, size);
|
||||||
|
test_ssp<trim>(data, size);
|
||||||
|
test_ssp<quote, escape>(data, size);
|
||||||
|
test_ssp<escape, quote, multiline_r, trim>(data, size);
|
||||||
|
test_ssp<escape, quote, multiline_r, trim, ss::ignore_empty>(data, size);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
74
.github/workflows/coverage.yml
vendored
Normal file
74
.github/workflows/coverage.yml
vendored
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
name: coverage-ci
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- feature/**
|
||||||
|
- improvement/**
|
||||||
|
- bugfix/**
|
||||||
|
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- feature/**
|
||||||
|
- improvement/**
|
||||||
|
- bugfix/**
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test_coverage:
|
||||||
|
if: >-
|
||||||
|
! contains(toJSON(github.event.commits.*.message), '[skip ci]') &&
|
||||||
|
! contains(toJSON(github.event.commits.*.message), '[skip github]')
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
name: "Coverage"
|
||||||
|
|
||||||
|
container:
|
||||||
|
image: gcc:latest
|
||||||
|
options: -v /usr/local:/host_usr_local
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v1
|
||||||
|
|
||||||
|
- uses: friendlyanon/fetch-core-count@v1
|
||||||
|
id: cores
|
||||||
|
|
||||||
|
- name: CMake
|
||||||
|
run: echo "/host_usr_local/bin" >> $GITHUB_PATH
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: script/ci_install_deps.sh
|
||||||
|
|
||||||
|
- name: Install test coverage tools
|
||||||
|
run: |
|
||||||
|
apt update
|
||||||
|
apt install -y gcovr
|
||||||
|
|
||||||
|
- name: Install lcov2.0
|
||||||
|
run: script/ci_install_lcov.sh
|
||||||
|
|
||||||
|
- name: Configure
|
||||||
|
run: cmake -S test -B build -D CMAKE_BUILD_TYPE=Debug -D CMAKE_CXX_FLAGS="-Wall -fprofile-arcs -ftest-coverage --coverage -fno-elide-constructors -fno-default-inline"
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: cmake --build build -j ${{steps.cores.outputs.count}}
|
||||||
|
|
||||||
|
- name: Run
|
||||||
|
working-directory: build
|
||||||
|
run: ctest --output-on-failure
|
||||||
|
|
||||||
|
- name: Generate coverage report
|
||||||
|
run: |
|
||||||
|
lcov -d . -c -o out.info --rc branch_coverage=1 --no-external --filter branch --filter line --ignore-errors mismatch
|
||||||
|
lcov -e out.info '*include/ss*hpp' -o filtered.info
|
||||||
|
|
||||||
|
- name: Invoke coveralls
|
||||||
|
uses: coverallsapp/github-action@v2
|
||||||
|
with:
|
||||||
|
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||||
|
file: filtered.info
|
||||||
|
format: lcov
|
43
.github/workflows/fuzz.yml
vendored
Normal file
43
.github/workflows/fuzz.yml
vendored
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
name: fuzz-ci
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- feature/**
|
||||||
|
- improvement/**
|
||||||
|
- bugfix/**
|
||||||
|
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- feature/**
|
||||||
|
- improvement/**
|
||||||
|
- bugfix/**
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
clang_tests:
|
||||||
|
if: >-
|
||||||
|
! contains(toJSON(github.event.commits.*.message), '[skip ci]') &&
|
||||||
|
! contains(toJSON(github.event.commits.*.message), '[skip github]')
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
name: "Fuzzing"
|
||||||
|
|
||||||
|
container:
|
||||||
|
image: silkeh/clang:15
|
||||||
|
options: -v /usr/local:/host_usr_local
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v1
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
working-directory: .github/fuzz
|
||||||
|
run: make
|
||||||
|
|
||||||
|
- name: Run
|
||||||
|
working-directory: .github/fuzz
|
||||||
|
run: make run
|
55
.github/workflows/macos-apple-clang.yml
vendored
Normal file
55
.github/workflows/macos-apple-clang.yml
vendored
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
name: macos-apple-clang-ci
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- feature/**
|
||||||
|
- improvement/**
|
||||||
|
- bugfix/**
|
||||||
|
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- feature/**
|
||||||
|
- improvement/**
|
||||||
|
- bugfix/**
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
clang_tests:
|
||||||
|
if: >-
|
||||||
|
! contains(toJSON(github.event.commits.*.message), '[skip ci]') &&
|
||||||
|
! contains(toJSON(github.event.commits.*.message), '[skip github]')
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
xcode: ['13.4.1', '14.1']
|
||||||
|
type: [Release, Debug]
|
||||||
|
|
||||||
|
runs-on: macos-12
|
||||||
|
|
||||||
|
env:
|
||||||
|
DEVELOPER_DIR: /Applications/Xcode_${{matrix.xcode}}.app/Contents/Developer
|
||||||
|
|
||||||
|
name: "Xcode ${{matrix.xcode}}: ${{matrix.type}}"
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- uses: friendlyanon/fetch-core-count@v1
|
||||||
|
id: cores
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: script/ci_install_deps.sh
|
||||||
|
|
||||||
|
- name: Configure
|
||||||
|
run: cmake -S test -B build -DCMAKE_BUILD_TYPE=${{matrix.type}}
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: cmake --build build -j ${{steps.cores.outputs.count}}
|
||||||
|
|
||||||
|
- name: Run
|
||||||
|
working-directory: build
|
||||||
|
run: ctest --output-on-failure
|
51
.github/workflows/single-header.yml
vendored
Normal file
51
.github/workflows/single-header.yml
vendored
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
name: single-header-ci
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- feature/**
|
||||||
|
- improvement/**
|
||||||
|
- bugfix/**
|
||||||
|
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- feature/**
|
||||||
|
- improvement/**
|
||||||
|
- bugfix/**
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
single_header_tests:
|
||||||
|
if: >-
|
||||||
|
! contains(toJSON(github.event.commits.*.message), '[skip ci]') &&
|
||||||
|
! contains(toJSON(github.event.commits.*.message), '[skip github]')
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
name: "Single Header Test"
|
||||||
|
|
||||||
|
container:
|
||||||
|
image: gcc:latest
|
||||||
|
options: -v /usr/local:/host_usr_local
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v1
|
||||||
|
|
||||||
|
- uses: friendlyanon/fetch-core-count@v1
|
||||||
|
id: cores
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y git
|
||||||
|
|
||||||
|
- name: Single header update check
|
||||||
|
run: |
|
||||||
|
script/single_header_generator.py > tmp.hpp
|
||||||
|
diff ssp.hpp tmp.hpp
|
||||||
|
|
||||||
|
- name: Single header compile check
|
||||||
|
run: ./test/test_single_header.sh
|
22
.github/workflows/ubuntu-latest-clang.yml
vendored
22
.github/workflows/ubuntu-latest-clang.yml
vendored
@ -1,6 +1,8 @@
|
|||||||
name: ubuntu-latest-clang-ci
|
name: ubuntu-latest-clang-ci
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
@ -23,20 +25,22 @@ jobs:
|
|||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
version: [11, 10, 9, 8, 7]
|
# cmake clang12 is not able to compile a simple test program.
|
||||||
|
# /usr/bin/ld: cannot find -lunwind
|
||||||
|
version: ['latest', '15', '14', '13', '11', '10', '9', '8', '7']
|
||||||
|
type: [Release, Debug]
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
name: Clang ${{ matrix.version }}
|
name: "Clang ${{matrix.version}}: ${{matrix.type}}"
|
||||||
|
|
||||||
container:
|
container:
|
||||||
image: teeks99/clang-ubuntu:${{ matrix.version }}
|
image: silkeh/clang:${{matrix.version}}
|
||||||
|
|
||||||
options: -v /usr/local:/host_usr_local
|
options: -v /usr/local:/host_usr_local
|
||||||
|
|
||||||
env:
|
env:
|
||||||
CC: clang-${{ matrix.version }}
|
CC: clang
|
||||||
CXX: clang++-${{ matrix.version }}
|
CXX: clang++
|
||||||
CXXFLAGS: -stdlib=libc++
|
CXXFLAGS: -stdlib=libc++
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@ -55,11 +59,11 @@ jobs:
|
|||||||
script/ci_install_deps.sh
|
script/ci_install_deps.sh
|
||||||
|
|
||||||
- name: Configure
|
- name: Configure
|
||||||
run: cmake -S test -B build -D CMAKE_BUILD_TYPE=Debug
|
run: cmake -S test -B build -DCMAKE_BUILD_TYPE=${{matrix.type}}
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: cmake --build build -j ${{ steps.cores.outputs.count }}
|
run: cmake --build build -j ${{steps.cores.outputs.count}}
|
||||||
|
|
||||||
- name: Run
|
- name: Run
|
||||||
working-directory: build
|
working-directory: build
|
||||||
run: ctest --output-on-failure -j ${{ steps.cores.outputs.count }}
|
run: ctest --output-on-failure
|
||||||
|
16
.github/workflows/ubuntu-latest-gcc.yml
vendored
16
.github/workflows/ubuntu-latest-gcc.yml
vendored
@ -1,6 +1,8 @@
|
|||||||
name: ubuntu-latest-gcc-ci
|
name: ubuntu-latest-gcc-ci
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
@ -23,15 +25,15 @@ jobs:
|
|||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
version: [10, 9, 8]
|
version: ['latest', '12', '11', '10', '9', '8']
|
||||||
|
type: [Release, Debug]
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
name: GCC ${{ matrix.version }}
|
name: "GCC ${{matrix.version}}: ${{matrix.type}}"
|
||||||
|
|
||||||
container:
|
container:
|
||||||
image: gcc:${{ matrix.version }}
|
image: gcc:${{matrix.version}}
|
||||||
|
|
||||||
options: -v /usr/local:/host_usr_local
|
options: -v /usr/local:/host_usr_local
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@ -47,11 +49,11 @@ jobs:
|
|||||||
run: script/ci_install_deps.sh
|
run: script/ci_install_deps.sh
|
||||||
|
|
||||||
- name: Configure
|
- name: Configure
|
||||||
run: cmake -S test -B build -D CMAKE_BUILD_TYPE=Debug
|
run: cmake -S test -B build -D CMAKE_BUILD_TYPE=${{matrix.type}}
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: cmake --build build -j ${{ steps.cores.outputs.count }}
|
run: cmake --build build -j ${{steps.cores.outputs.count}}
|
||||||
|
|
||||||
- name: Run
|
- name: Run
|
||||||
working-directory: build
|
working-directory: build
|
||||||
run: ctest --output-on-failure -j ${{ steps.cores.outputs.count }}
|
run: ctest --output-on-failure
|
||||||
|
62
.github/workflows/ubuntu-latest-icc.yml
vendored
62
.github/workflows/ubuntu-latest-icc.yml
vendored
@ -1,6 +1,8 @@
|
|||||||
name: ubuntu-latest-icc-ci
|
name: ubuntu-latest-icc-ci
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
@ -17,7 +19,7 @@ on:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
LINUX_BASEKIT_URL: https://registrationcenter-download.intel.com/akdlm/irc_nas/17431/l_BaseKit_p_2021.1.0.2659_offline.sh
|
LINUX_BASEKIT_URL: https://registrationcenter-download.intel.com/akdlm/irc_nas/17431/l_BaseKit_p_2021.1.0.2659_offline.sh
|
||||||
LINUX_HPCKIT_URL:
|
LINUX_HPCKIT_URL:
|
||||||
https://registrationcenter-download.intel.com/akdlm/irc_nas/17427/l_HPCKit_p_2021.1.0.2684_offline.sh
|
https://registrationcenter-download.intel.com/akdlm/irc_nas/17427/l_HPCKit_p_2021.1.0.2684_offline.sh
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@ -27,6 +29,7 @@ jobs:
|
|||||||
! contains(toJSON(github.event.commits.*.message), '[skip github]')
|
! contains(toJSON(github.event.commits.*.message), '[skip github]')
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
shell: bash
|
shell: bash
|
||||||
@ -36,38 +39,39 @@ jobs:
|
|||||||
options: -v /usr/local:/host_usr_local
|
options: -v /usr/local:/host_usr_local
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: cache install
|
- name: cache install
|
||||||
id: cache-install
|
id: cache-install
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v2
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
/opt/intel/oneapi/compiler
|
/opt/intel/oneapi/compiler
|
||||||
key: >-
|
key: >-
|
||||||
install-${{ env.LINUX_HPCKIT_URL }}-
|
install-${{env.LINUX_HPCKIT_URL}}-
|
||||||
${{ env.LINUX_CPP_COMPONENTS_WEB }}-
|
${{env.LINUX_CPP_COMPONENTS_WEB}}-
|
||||||
compiler-${{ hashFiles('**/scripts/cache_exclude_linux.sh') }}
|
compiler-${{hashFiles('**/scripts/cache_exclude_linux.sh')}}
|
||||||
|
|
||||||
- name: Install icc
|
- name: Install icc
|
||||||
run: script/ci_install_icc.sh $LINUX_HPCKIT_URL $LINUX_CPP_COMPONENTS_WEB
|
run: >-
|
||||||
|
script/ci_install_icc.sh $LINUX_HPCKIT_URL $LINUX_CPP_COMPONENTS_WEB
|
||||||
|
|
||||||
- name: CMake
|
- name: CMake
|
||||||
run: echo "/host_usr_local/bin" >> $GITHUB_PATH
|
run: echo "/host_usr_local/bin" >> $GITHUB_PATH
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: script/ci_install_deps.sh
|
run: script/ci_install_deps.sh
|
||||||
|
|
||||||
- name: Configure
|
- name: Configure
|
||||||
run: >-
|
run: >-
|
||||||
source script/ci_setup_icc.sh &&
|
source script/ci_setup_icc.sh &&
|
||||||
cmake -S test -B build -D CMAKE_BUILD_TYPE=Debug
|
cmake -S test -B build -D CMAKE_BUILD_TYPE=Debug
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: >-
|
run: >-
|
||||||
source script/ci_setup_icc.sh &&
|
source script/ci_setup_icc.sh &&
|
||||||
cmake --build build -j ${{ steps.cores.outputs.count }}
|
cmake --build build -j ${{steps.cores.outputs.count}}
|
||||||
|
|
||||||
- name: Run
|
- name: Run
|
||||||
working-directory: build
|
working-directory: build
|
||||||
run: ctest --output-on-failure -j ${{ steps.cores.outputs.count }}
|
run: ctest --output-on-failure
|
||||||
|
65
.github/workflows/win-msvc.yml
vendored
Normal file
65
.github/workflows/win-msvc.yml
vendored
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
name: win-msvc-ci
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- feature/**
|
||||||
|
- improvement/**
|
||||||
|
- bugfix/**
|
||||||
|
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- feature/**
|
||||||
|
- improvement/**
|
||||||
|
- bugfix/**
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
ci:
|
||||||
|
if: >-
|
||||||
|
! contains(toJSON(github.event.commits.*.message), '[skip ci]') &&
|
||||||
|
! contains(toJSON(github.event.commits.*.message), '[skip github]')
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
config:
|
||||||
|
- os: windows-2019
|
||||||
|
vs: "Visual Studio 16 2019"
|
||||||
|
|
||||||
|
- os: windows-latest
|
||||||
|
vs: "Visual Studio 17 2022"
|
||||||
|
|
||||||
|
build: [Debug, Release]
|
||||||
|
platform: [Win32, x64]
|
||||||
|
|
||||||
|
runs-on: ${{matrix.config.os}}
|
||||||
|
|
||||||
|
name: "${{matrix.config.vs}}: ${{matrix.platform}}: ${{matrix.build}}"
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: script/ci_install_deps.sh
|
||||||
|
|
||||||
|
- name: Configure
|
||||||
|
run: >-
|
||||||
|
cmake -S test -B build -D CMAKE_BUILD_TYPE=${{matrix.build}}
|
||||||
|
-G "${{matrix.config.vs}}" -A ${{matrix.platform}}
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: cmake --build build -j ${{steps.cores.outputs.count}}
|
||||||
|
|
||||||
|
- name: Run
|
||||||
|
working-directory: build
|
||||||
|
run: >-
|
||||||
|
ctest -C Debug --output-on-failure
|
47
.github/workflows/win-msys2-clang.yml
vendored
47
.github/workflows/win-msys2-clang.yml
vendored
@ -1,6 +1,8 @@
|
|||||||
name: win-msys2-clang-ci
|
name: win-msys2-clang-ci
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
@ -21,57 +23,54 @@ jobs:
|
|||||||
! contains(toJSON(github.event.commits.*.message), '[skip ci]') &&
|
! contains(toJSON(github.event.commits.*.message), '[skip ci]') &&
|
||||||
! contains(toJSON(github.event.commits.*.message), '[skip github]')
|
! contains(toJSON(github.event.commits.*.message), '[skip github]')
|
||||||
|
|
||||||
name: ${{ matrix.msystem }}
|
|
||||||
runs-on: windows-latest
|
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
shell: msys2 {0}
|
shell: msys2 {0}
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
os: [windows-2019, windows-latest]
|
||||||
|
type: [Release, Debug]
|
||||||
|
config:
|
||||||
- msystem: "MINGW64"
|
- msystem: "MINGW64"
|
||||||
install: >-
|
install: >-
|
||||||
git mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja
|
git mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja
|
||||||
mingw-w64-x86_64-clang
|
mingw-w64-x86_64-clang
|
||||||
type: Release
|
|
||||||
- msystem: "MINGW32"
|
- msystem: "MINGW32"
|
||||||
install: >-
|
install: >-
|
||||||
git mingw-w64-i686-cmake mingw-w64-i686-ninja
|
git mingw-w64-i686-cmake mingw-w64-i686-ninja
|
||||||
mingw-w64-i686-clang
|
mingw-w64-i686-clang
|
||||||
type: Release
|
|
||||||
- msystem: "MINGW64"
|
runs-on: ${{matrix.os}}
|
||||||
install: >-
|
|
||||||
git mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja
|
name: "${{matrix.config.msystem}}: ${{matrix.os}}: ${{matrix.type}}"
|
||||||
mingw-w64-x86_64-clang
|
|
||||||
type: Debug
|
|
||||||
- msystem: "MINGW32"
|
|
||||||
install: >-
|
|
||||||
git mingw-w64-i686-cmake mingw-w64-i686-ninja
|
|
||||||
mingw-w64-i686-clang
|
|
||||||
type: Debug
|
|
||||||
env:
|
env:
|
||||||
CMAKE_GENERATOR: Ninja
|
CMAKE_GENERATOR: Ninja
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
- uses: msys2/setup-msys2@v2
|
- uses: msys2/setup-msys2@v2
|
||||||
with:
|
with:
|
||||||
update: true
|
update: true
|
||||||
msystem: ${{ matrix.msystem }}
|
msystem: ${{matrix.config.msystem}}
|
||||||
install: ${{ matrix.install }}
|
install: ${{matrix.config.install}}
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: script/ci_install_deps.sh
|
run: script/ci_install_deps.sh
|
||||||
|
|
||||||
- name: Configure
|
- name: Configure
|
||||||
run: >-
|
run: >-
|
||||||
cmake -DCMAKE_CXX_COMPILER=clang++ -S test -B build
|
cmake -DCMAKE_CXX_COMPILER=clang++ -S test -B build
|
||||||
-DCMAKE_BUILD_TYPE=Debug
|
-DCMAKE_BUILD_TYPE=${{matrix.type}}
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: cmake --build build -j ${{ steps.cores.outputs.count }}
|
run: cmake --build build -j ${{steps.cores.outputs.count}}
|
||||||
|
|
||||||
- name: Run
|
- name: Run
|
||||||
working-directory: build
|
working-directory: build
|
||||||
run: ctest --output-on-failure -j ${{ steps.cores.outputs.count }}
|
run: ctest --output-on-failure
|
||||||
|
43
.github/workflows/win-msys2-gcc.yml
vendored
43
.github/workflows/win-msys2-gcc.yml
vendored
@ -1,6 +1,8 @@
|
|||||||
name: win-msys2-gcc-ci
|
name: win-msys2-gcc-ci
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
@ -21,55 +23,52 @@ jobs:
|
|||||||
! contains(toJSON(github.event.commits.*.message), '[skip ci]') &&
|
! contains(toJSON(github.event.commits.*.message), '[skip ci]') &&
|
||||||
! contains(toJSON(github.event.commits.*.message), '[skip github]')
|
! contains(toJSON(github.event.commits.*.message), '[skip github]')
|
||||||
|
|
||||||
name: ${{ matrix.msystem }}
|
|
||||||
runs-on: windows-latest
|
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
shell: msys2 {0}
|
shell: msys2 {0}
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
os: [windows-2019, windows-latest]
|
||||||
|
type: [Release, Debug]
|
||||||
|
config:
|
||||||
- msystem: "MINGW64"
|
- msystem: "MINGW64"
|
||||||
install: >-
|
install: >-
|
||||||
git mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja
|
git mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja
|
||||||
mingw-w64-x86_64-gcc
|
mingw-w64-x86_64-gcc
|
||||||
type: Release
|
|
||||||
- msystem: "MINGW32"
|
|
||||||
install: >-
|
|
||||||
git mingw-w64-i686-cmake mingw-w64-i686-ninja
|
|
||||||
mingw-w64-i686-gcc
|
|
||||||
type: Release
|
|
||||||
- msystem: "MINGW64"
|
|
||||||
install: >-
|
|
||||||
git mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja
|
|
||||||
mingw-w64-x86_64-gcc
|
|
||||||
type: Debug
|
|
||||||
- msystem: "MINGW32"
|
- msystem: "MINGW32"
|
||||||
install: >-
|
install: >-
|
||||||
git mingw-w64-i686-cmake mingw-w64-i686-ninja
|
git mingw-w64-i686-cmake mingw-w64-i686-ninja
|
||||||
mingw-w64-i686-gcc
|
mingw-w64-i686-gcc
|
||||||
type: Debug
|
|
||||||
|
runs-on: ${{matrix.os}}
|
||||||
|
|
||||||
|
name: "${{matrix.config.msystem}}: ${{matrix.os}}: ${{matrix.type}}"
|
||||||
|
|
||||||
env:
|
env:
|
||||||
CMAKE_GENERATOR: Ninja
|
CMAKE_GENERATOR: Ninja
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
- uses: msys2/setup-msys2@v2
|
- uses: msys2/setup-msys2@v2
|
||||||
with:
|
with:
|
||||||
update: true
|
update: true
|
||||||
msystem: ${{ matrix.msystem }}
|
msystem: ${{matrix.config.msystem}}
|
||||||
install: ${{ matrix.install }}
|
install: ${{matrix.config.install}}
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: script/ci_install_deps.sh
|
run: script/ci_install_deps.sh
|
||||||
|
|
||||||
- name: Configure
|
- name: Configure
|
||||||
run: cmake -S test -B build -D CMAKE_BUILD_TYPE=Debug
|
run: cmake -S test -B build -D CMAKE_BUILD_TYPE=${{matrix.type}}
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: cmake --build build -j ${{ steps.cores.outputs.count }}
|
run: cmake --build build -j ${{steps.cores.outputs.count}}
|
||||||
|
|
||||||
- name: Run
|
- name: Run
|
||||||
working-directory: build
|
working-directory: build
|
||||||
run: ctest --output-on-failure -j ${{ steps.cores.outputs.count }}
|
run: ctest --output-on-failure
|
||||||
|
6
.gitignore
vendored
6
.gitignore
vendored
@ -1,10 +1,10 @@
|
|||||||
compile_commands.json
|
compile_commands.json
|
||||||
.clang-format
|
.clang-format
|
||||||
.ccls-cache/*
|
.clang-tidy
|
||||||
|
.ccls-cache/
|
||||||
|
.cache/
|
||||||
experiment/
|
experiment/
|
||||||
build/
|
build/
|
||||||
hbuild/
|
hbuild/
|
||||||
subprojects/*
|
subprojects/*
|
||||||
!subprojects/*.wrap
|
!subprojects/*.wrap
|
||||||
ssp.cpp
|
|
||||||
ssp.bin
|
|
||||||
|
@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.14)
|
|||||||
|
|
||||||
project(
|
project(
|
||||||
ssp
|
ssp
|
||||||
VERSION 1.4.0
|
VERSION 1.8.0
|
||||||
DESCRIPTION "csv parser"
|
DESCRIPTION "csv parser"
|
||||||
HOMEPAGE_URL "https://github.com/red0124/ssp"
|
HOMEPAGE_URL "https://github.com/red0124/ssp"
|
||||||
LANGUAGES CXX
|
LANGUAGES CXX
|
||||||
|
297
README.md
297
README.md
@ -8,25 +8,29 @@
|
|||||||
```
|
```
|
||||||
|
|
||||||
[](https://opensource.org/licenses/MIT)
|
[](https://opensource.org/licenses/MIT)
|
||||||

|
[](https://coveralls.io/github/red0124/ssp?branch=master)
|
||||||

|
[](https://github.com/red0124/ssp/actions/workflows/fuzz.yml)
|
||||||

|
[](https://github.com/red0124/ssp/actions/workflows/single-header.yml)
|
||||||

|
[](https://github.com/red0124/ssp/actions/workflows/ubuntu-latest-gcc.yml)
|
||||||

|
[](https://github.com/red0124/ssp/actions/workflows/ubuntu-latest-clang.yml)
|
||||||
|
[](https://github.com/red0124/ssp/actions/workflows/ubuntu-latest-icc.yml)
|
||||||
|
[](https://github.com/red0124/ssp/actions/workflows/win-msys2-gcc.yml)
|
||||||
|
[](https://github.com/red0124/ssp/actions/workflows/win-msys2-clang.yml)
|
||||||
|
[](https://github.com/red0124/ssp/actions/workflows/win-msvc.yml)
|
||||||
|
[](https://github.com/red0124/ssp/actions/workflows/macos-apple-clang.yml)
|
||||||
|
|
||||||
A header only "csv" parser which is fast and versatile with modern C++ api. Requires compiler with C++17 support. [Can also be used to convert strings to specific types.](#The-converter)
|
A header only CSV parser which is fast and versatile with modern C++ API. Requires compiler with C++17 support. [Can also be used to efficiently convert strings to specific types.](#the-converter)
|
||||||
|
|
||||||
Conversion for floating point values invoked using [fast-float](https://github.com/fastfloat/fast_float) .
|
Conversion for floating point values invoked using [fast-float](https://github.com/fastfloat/fast_float) . \
|
||||||
Function traits taken from [qt-creator](https://code.woboq.org/qt5/qt-creator/src/libs/utils/functiontraits.h.html) .
|
Function traits taken from *qt-creator* .
|
||||||
|
|
||||||
# Example
|
# Example
|
||||||
Lets say we have a csv file containing students in a given format '$name,$age,$grade' and we want to parse and print all the valid values:
|
Lets say we have a CSV file containing students in a given format (Id,Age,Grade) and we want to parse and print all the valid values:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
$ cat students.csv
|
$ cat students.csv
|
||||||
James Bailey,65,2.5
|
James Bailey,65,2.5
|
||||||
Brian S. Wolfe,40,1.9
|
Brian S. Wolfe,40,1.9
|
||||||
Nathan Fielder,37,Really good grades
|
|
||||||
Bill (Heath) Gates,65,3.3
|
Bill (Heath) Gates,65,3.3
|
||||||
```
|
```
|
||||||
```cpp
|
```cpp
|
||||||
@ -34,12 +38,10 @@ Bill (Heath) Gates,65,3.3
|
|||||||
#include <ss/parser.hpp>
|
#include <ss/parser.hpp>
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
ss::parser p{"students.csv", ","};
|
ss::parser<ss::throw_on_error> p{"students.csv"};
|
||||||
|
|
||||||
for(const auto& [name, age, grade] : p.iterate<std::string, int, float>()) {
|
for (const auto& [id, age, grade] : p.iterate<std::string, int, float>()) {
|
||||||
if (p.valid()) {
|
std::cout << id << ' ' << age << ' ' << grade << std::endl;
|
||||||
std::cout << name << ' ' << age << ' ' << grade << std::endl;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@ -55,14 +57,15 @@ Bill (Heath) Gates 65 3.3
|
|||||||
# Features
|
# Features
|
||||||
* [Works on any type](#custom-conversions)
|
* [Works on any type](#custom-conversions)
|
||||||
* Easy to use
|
* Easy to use
|
||||||
* No exceptions
|
* Can work without exceptions
|
||||||
* [Works with headers](#headers)
|
* [Works with headers](#headers)
|
||||||
* [Works with quotes, escapes and spacings](#setup)
|
* [Works with quotes, escapes and spacings](#setup)
|
||||||
|
* [Works with CSV data stored in buffers](#buffer-mode)
|
||||||
* [Works with values containing new lines](#multiline)
|
* [Works with values containing new lines](#multiline)
|
||||||
* [Columns and rows can be ignored](#special-types)
|
* [Columns and rows can be ignored](#special-types)
|
||||||
* Works with any type of delimiter
|
* [Works with any type of delimiter](#delimiter)
|
||||||
* Can return whole objects composed of converted values
|
* Can return whole objects composed of converted values
|
||||||
* [Descriptive error handling can be enabled](#error-handling)
|
* [Error handling can be configured](#error-handling)
|
||||||
* [Restrictions can be added for each column](#restrictions)
|
* [Restrictions can be added for each column](#restrictions)
|
||||||
* [Works with `std::optional` and `std::variant`](#special-types)
|
* [Works with `std::optional` and `std::variant`](#special-types)
|
||||||
* Works with **`CRLF`** and **`LF`**
|
* Works with **`CRLF`** and **`LF`**
|
||||||
@ -71,7 +74,7 @@ Bill (Heath) Gates 65 3.3
|
|||||||
|
|
||||||
# Single header
|
# Single header
|
||||||
|
|
||||||
The library can be used with a single header file **`ssp.hpp`**, but it sufferes a slight performance loss when converting floating point values since the **`fast_float`** library is not present within the file.
|
The library can be used with a single header file **`ssp.hpp`**, but it suffers a significant performance loss when converting floating point values since the **`fast_float`** library is not present within the file.
|
||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
|
|
||||||
@ -82,30 +85,30 @@ $ cmake --configure .
|
|||||||
$ sudo make install
|
$ sudo make install
|
||||||
```
|
```
|
||||||
|
|
||||||
*Note, this will also install the fast_float library*
|
*Note, this will also install the fast_float library.*\
|
||||||
The library supports [CMake](#Cmake) and [meson](#Meson) build systems
|
The library supports [CMake](#Cmake) and [meson](#Meson) build systems
|
||||||
|
|
||||||
# Usage
|
# Usage
|
||||||
|
|
||||||
## Headers
|
## Headers
|
||||||
|
|
||||||
The parser can be told to use only certain columns by parsing the header. This can be done by using the **`use_fields`** method. It accepts any number of string-like arguments or even an **`std::vector<std::string>`** with the field names. If any of the fields are not found within the header or if any fields are defined multiple times it will result in an error.
|
The parser can be told to use only certain columns by parsing the header. This can be done with the **`use_fields`** method. It accepts any number of string-like arguments or even an **`std::vector<std::string>`** with the field names. If any of the fields are not found within the header or if any fields are defined multiple times it will result in an error.
|
||||||
```shell
|
```shell
|
||||||
$ cat students_with_header.csv
|
$ cat students_with_header.csv
|
||||||
Name,Age,Grade
|
Id,Age,Grade
|
||||||
James Bailey,65,2.5
|
James Bailey,65,2.5
|
||||||
Brian S. Wolfe,40,1.9
|
Brian S. Wolfe,40,1.9
|
||||||
Bill (Heath) Gates,65,3.3
|
Bill (Heath) Gates,65,3.3
|
||||||
```
|
```
|
||||||
```cpp
|
```cpp
|
||||||
// ...
|
// ...
|
||||||
ss::parser p{"students.csv", ","};
|
ss::parser<ss::throw_on_error> p{"students_with_header.csv"};
|
||||||
p.use_fields("Name", "Grade");
|
p.use_fields("Id", "Grade");
|
||||||
|
|
||||||
for(const auto& [name, grade] : p.iterate<std::string, float>()) {
|
for(const auto& [id, grade] : p.iterate<std::string, float>()) {
|
||||||
std::cout << name << ' ' << grade << std::endl;
|
std::cout << id << ' ' << grade << std::endl;
|
||||||
}
|
}
|
||||||
// ...
|
// ...
|
||||||
```
|
```
|
||||||
```shell
|
```shell
|
||||||
$ ./a.out
|
$ ./a.out
|
||||||
@ -113,62 +116,88 @@ James Bailey 2.5
|
|||||||
Brian S. Wolfe 1.9
|
Brian S. Wolfe 1.9
|
||||||
Bill (Heath) Gates 3.3
|
Bill (Heath) Gates 3.3
|
||||||
```
|
```
|
||||||
The header can be ignored using the **`ss::ignore_header`** [setup](#Setup) option or by calling the **`ignore_next`** metod after the parser has been constructed.
|
The header can be ignored using the **`ss::ignore_header`** [setup](#Setup) option or by calling the **`ignore_next`** method after the parser has been constructed. If the header has been ignored calling any method related to header usage will result in a compilation error.
|
||||||
```cpp
|
```cpp
|
||||||
ss::parser<ss::ignore_header> p{file_name};
|
ss::parser<ss::ignore_header> p{file_name};
|
||||||
```
|
```
|
||||||
The fields with which the parser works with can be modified at any given time. The praser can also check if a field is present within the header by using the **`field_exists`** method.
|
The fields with which the parser works with can be modified at any given time. The parser can also check if a field is present within the header by using the **`field_exists`** method.
|
||||||
```cpp
|
```cpp
|
||||||
// ...
|
// ...
|
||||||
ss::parser p{"students.csv", ","};
|
ss::parser<ss::throw_on_error> p{"students_with_header.csv"};
|
||||||
p.use_fields("Name", "Grade");
|
p.use_fields("Grade");
|
||||||
|
|
||||||
const auto& [name, grade] = p.get_next<std::string, float>();
|
const auto& grade = p.get_next<std::string>();
|
||||||
std::cout << name << ' ' << grade << std::endl;
|
std::cout << grade << std::endl;
|
||||||
|
|
||||||
if (p.field_exists("Age")) {
|
if (p.field_exists("Id")) {
|
||||||
p.use_fields("Grade", "Name", "Age");
|
p.use_fields("Grade", "Id");
|
||||||
for (const auto& [grade, name, age] :
|
for (const auto& [grade, id] : p.iterate<float, std::string>()) {
|
||||||
p.iterate<float, std::string, int>()) {
|
std::cout << grade << ' ' << id << std::endl;
|
||||||
std::cout << grade << ' ' << name << ' ' << age << std::endl;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ...
|
// ...
|
||||||
```
|
```
|
||||||
```shell
|
```shell
|
||||||
$ ./a.out
|
$ ./a.out
|
||||||
James Bailey 2.5
|
2.5
|
||||||
1.9 Brian S. Wolfe 40
|
1.9 Brian S. Wolfe
|
||||||
3.3 Bill (Heath) Gates 65
|
3.3 Bill (Heath) Gates
|
||||||
```
|
```
|
||||||
|
The header is parsed with the same rules as other rows, the only difference is that **`multiline`** will be disabled when parsing the header. To get the data that is
|
||||||
|
present in the header as a **`std::vector<std::string>`**, the **`header`** method can be used, and to get the header line before it has been parsed, the **`raw_header`** method can be used:
|
||||||
|
```cpp
|
||||||
|
// ...
|
||||||
|
ss::parser<ss::throw_on_error> p{"students_with_header.csv"};
|
||||||
|
|
||||||
|
std::cout << p.raw_header() << std::endl;
|
||||||
|
|
||||||
|
for (const auto& field: p.header()) {
|
||||||
|
std::cout << "> " << field << std::endl;
|
||||||
|
}
|
||||||
|
// ...
|
||||||
|
```
|
||||||
|
```shell
|
||||||
|
$ ./a.out
|
||||||
|
Id,Age,Grade
|
||||||
|
> Id
|
||||||
|
> Age
|
||||||
|
> Grade
|
||||||
|
```
|
||||||
|
Methods related to headers can also fail, the error handling of these is done in the same way as for other methods.
|
||||||
|
|
||||||
## Conversions
|
## Conversions
|
||||||
An alternate loop to the example above would look like:
|
An alternate loop to the example above would look like:
|
||||||
```cpp
|
```cpp
|
||||||
while(!p.eof()) {
|
// ...
|
||||||
auto [name, age, grade] = p.get_next<std::string, int, float>();
|
ss::parser p{"students.csv"};
|
||||||
|
|
||||||
|
while (!p.eof()) {
|
||||||
|
const auto& [id, age, grade] = p.get_next<std::string, int, float>();
|
||||||
|
|
||||||
if (p.valid()) {
|
if (p.valid()) {
|
||||||
std::cout << name << ' ' << age << ' ' << grade << std::endl;
|
std::cout << id << ' ' << age << ' ' << grade << std::endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// ...
|
||||||
```
|
```
|
||||||
|
|
||||||
The alternate example will be used to show some of the features of the library. The **`get_next`** method returns a tuple of objects specified inside the template type list.
|
The alternate example with exceptions disabled will be used to show some of the features of the library. The **`get_next`** method returns a tuple of objects specified inside the template type list.
|
||||||
|
|
||||||
If a conversion could not be applied, the method would return a tuple of default constructed objects, and the **`valid`** method would return **`false`**, for example if the third (grade) column in our csv could not be converted to a float the conversion would fail.
|
If a conversion could not be applied, the method would return a tuple of default constructed objects, and the **`valid`** method would return **`false`**, for example if the third (grade) column in our CSV could not be converted to a float the conversion would fail.
|
||||||
|
|
||||||
If **`get_next`** is called with a **`tuple`** as template parameter it would behave identically to passing the same tuple parameters to **`get_next`**:
|
If **`get_next`** is called with a **`tuple`** as template parameter it would behave identically to passing the same tuple parameters to **`get_next`**:
|
||||||
```cpp
|
```cpp
|
||||||
using student = std::tuple<std::string, int, float>;
|
using student = std::tuple<std::string, int, float>;
|
||||||
|
|
||||||
// returns std::tuple<std::string, int, float>
|
// returns std::tuple<std::string, int, float>
|
||||||
auto [name, age, grade] = p.get_next<student>();
|
auto [id, age, grade] = p.get_next<student>();
|
||||||
```
|
```
|
||||||
*Note, it does not always return a student tuple since the returned tuples parameters may be altered as explained below (no void, no restrictions, ...)*
|
*Note, it does not always return the specified tuple since the returned tuples parameters may be altered as explained below (no void, no restrictions, ...)*
|
||||||
|
|
||||||
Whole objects can be returned using the **`get_object`** function which takes the tuple, created in a similar way as **`get_next`** does it, and creates an object out of it:
|
Whole objects can be returned using the **`get_object`** function which takes the tuple, created in a similar way as **`get_next`** does it, and creates an object out of it:
|
||||||
```cpp
|
```cpp
|
||||||
struct student {
|
struct student {
|
||||||
std::string name;
|
std::string id;
|
||||||
int age;
|
int age;
|
||||||
float grade;
|
float grade;
|
||||||
};
|
};
|
||||||
@ -179,34 +208,46 @@ auto student = p.get_object<student, std::string, int, float>();
|
|||||||
```
|
```
|
||||||
This works with any object if the constructor could be invoked using the template arguments given to **`get_object`**:
|
This works with any object if the constructor could be invoked using the template arguments given to **`get_object`**:
|
||||||
```cpp
|
```cpp
|
||||||
// returns std::vector<std::string> containing 3 elements
|
// returns std::vector<std::string> containing 2 elements
|
||||||
auto vec = p.get_object<std::vector<std::string>, std::string, std::string,
|
auto vec = p.get_object<std::vector<std::string>, std::string, std::string>();
|
||||||
std::string>();
|
|
||||||
```
|
```
|
||||||
An iteration loop as in the first example which returns objects would look like:
|
An iterator loop as in the first example which returns objects would look like:
|
||||||
```cpp
|
```cpp
|
||||||
for(const auto& student : p.iterate_object<student, std::string, int, float>()) {
|
for (const student& s : p.iterate_object<student, std::string, int, float>()) {
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
And finally, using something I personally like to do, a struct (class) with a **`tied`** method which returns a tuple of references to to the members of the struct.
|
And finally, using something I personally like to do, a struct (class) with a **`tied`** method which returns a tuple of references to to the members of the struct.
|
||||||
```cpp
|
```cpp
|
||||||
struct student {
|
struct student {
|
||||||
std::string name;
|
std::string id;
|
||||||
int age;
|
int age;
|
||||||
float grade;
|
float grade;
|
||||||
|
|
||||||
auto tied() { return std::tie(name, age, grade); }
|
auto tied() { return std::tie(id, age, grade); }
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
The method can be used to compare the object, serialize it, deserialize it, etc. Now **`get_next`** can accept such a struct and deduce the types to which to convert the csv.
|
The method can be used to compare the object, serialize it, deserialize it, etc. Now **`get_next`** can accept such a struct and deduce the types to which to convert the CSV.
|
||||||
```cpp
|
```cpp
|
||||||
// returns student
|
// returns student
|
||||||
auto s = p.get_next<student>();
|
auto s = p.get_next<student>();
|
||||||
```
|
```
|
||||||
This works with the iteration loop too.
|
This works with the iteration loop too.
|
||||||
*Note, the order in which the members of the tied method are returned must match the order of the elements in the csv*.
|
*Note, the order in which the members of the tied method are returned must match the order of the elements in the CSV*.
|
||||||
|
|
||||||
|
## Buffer mode
|
||||||
|
The parser also works with buffers containing CSV data instead of files. To parse buffer data with the parser simply create the parser by giving it the buffer, as **`const char*`**, and its size. The initial example using a buffer instead of a file would look similar to this:
|
||||||
|
```cpp
|
||||||
|
std::string buffer = "James Bailey,65,2.5\nBrian S. Wolfe,40,1.9\n";
|
||||||
|
|
||||||
|
ss::parser<ss::throw_on_error> p{buffer.c_str(), buffer.size()};
|
||||||
|
|
||||||
|
for (const auto& [id, age, grade] : p.iterate<std::string, int, float>()) {
|
||||||
|
std::cout << id << ' ' << age << ' ' << grade << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
```
|
||||||
## Setup
|
## Setup
|
||||||
By default, many of the features supported by the parser are disabled. They can be enabled within the template parameters of the parser. For example, to enable quoting and escaping the parser would look like:
|
By default, many of the features supported by the parser are disabled. They can be enabled within the template parameters of the parser. For example, to enable quoting and escaping the parser would look like:
|
||||||
```cpp
|
```cpp
|
||||||
@ -224,14 +265,21 @@ using my_setup = ss::setup<ss::escape<'\\'>, ss::quote<'"'>>;
|
|||||||
ss::parser<my_setup> p2{file_name};
|
ss::parser<my_setup> p2{file_name};
|
||||||
```
|
```
|
||||||
Invalid setups will be met with **`static_asserts`**.
|
Invalid setups will be met with **`static_asserts`**.
|
||||||
*Note, each setup parameter defined comes with a slight performance loss, so use them only if needed.*
|
*Note, most setup parameters defined come with a slight performance loss, so use them only if needed.*
|
||||||
|
|
||||||
|
### Delimiter
|
||||||
|
By default, **`,`** is used as the delimiter, a custom delimiter can be specified as the second constructor parameter.
|
||||||
|
```cpp
|
||||||
|
ss::parser p{file_name, "--"};
|
||||||
|
```
|
||||||
|
*Note, the delimiter can consist of multiple characters but the parser is slightly faster when using single character delimiters.*
|
||||||
|
|
||||||
### Empty lines
|
### Empty lines
|
||||||
Empty lines can be ignored by defining **`ss::ignore_empty`** within the setup parameters:
|
Empty lines can be ignored by defining **`ss::ignore_empty`** within the setup parameters:
|
||||||
```cpp
|
```cpp
|
||||||
ss::parser<ss::ignore_empty> p{file_name};
|
ss::parser<ss::ignore_empty> p{file_name};
|
||||||
```
|
```
|
||||||
If this setup option is not set then reading an empty line will result in an error (unless only one column is present within the csv).
|
If this setup option is not set then reading an empty line will result in an error (unless only one column is present within the CSV).
|
||||||
|
|
||||||
### Quoting
|
### Quoting
|
||||||
Quoting can be enabled by defining **`ss::quote`** within the setup parameters. A single character can be defined as the quoting character, for example to use **`"`** as a quoting character:
|
Quoting can be enabled by defining **`ss::quote`** within the setup parameters. A single character can be defined as the quoting character, for example to use **`"`** as a quoting character:
|
||||||
@ -280,7 +328,7 @@ Escaping and quoting can be used to leave the space if needed.
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Multiline
|
### Multiline
|
||||||
Multiline can be enabled by defining **`ss::multilne`** within the setup parameters. It enables the possibility to have the new line characters within rows. The new line character needs to be either escaped or within quotes so either **`ss::escape`** or **`ss::quote`** need to be enabled. There is a specific problem when using multiline, for example, if a row had an unterminated quote, the parser would assume it to be a new line within the row, so until another quote is found, it will treat it as one line which is fine usually, but it can cause the whole csv file to be treated as a single line by mistake. To prevent this **`ss::multiline_restricted`** can be used which accepts an unsigned number representing the maximum number of lines which can be allowed as a single multiline. Examples:
|
Multiline can be enabled by defining **`ss::multilne`** within the setup parameters. It enables the possibility to have the new line characters within rows. The new line character needs to be either escaped or within quotes so either **`ss::escape`** or **`ss::quote`** need to be enabled. There is a specific problem when using multiline, for example, if a row had an unterminated quote, the parser would assume it to be a new line within the row, so until another quote is found, it will treat it as one line which is fine usually, but it can cause the whole CSV file to be treated as a single line by mistake. To prevent this **`ss::multiline_restricted`** can be used which accepts an unsigned number representing the maximum number of lines which can be allowed as a single multiline. Examples:
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
ss::parser<ss::multiline, ss::quote<'\"'>, ss::escape<'\\'>> p{file_name};
|
ss::parser<ss::multiline, ss::quote<'\"'>, ss::escape<'\\'>> p{file_name};
|
||||||
@ -306,14 +354,11 @@ ss::parser<ss::escape<'\\'>,
|
|||||||
ss::trim<' ', '\t'>,
|
ss::trim<' ', '\t'>,
|
||||||
ss::multiline_restricted<5>> p{file_name};
|
ss::multiline_restricted<5>> p{file_name};
|
||||||
|
|
||||||
while(!p.eof()) {
|
for (const auto& [id, age, grade] : p.iterate<std::string, int, float>()) {
|
||||||
auto [name, age, grade] = p.get_next<std::string, int, float>();
|
if (p.valid()) {
|
||||||
if(!p.valid()) {
|
std::cout << "'" << id << ' ' << age << ' ' << grade << "'\n";
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
std::cout << "'" << name << ' ' << age << ' ' << grade << "'" << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
input:
|
input:
|
||||||
```
|
```
|
||||||
@ -334,22 +379,22 @@ Gates 65 3.3'
|
|||||||
```
|
```
|
||||||
## Special types
|
## Special types
|
||||||
|
|
||||||
Passing **`void`** makes the parser ignore a column. In the given example **`void`** could be given as the second template parameter to ignore the second (age) column in the csv, a tuple of only 2 parameters would be retuned:
|
Passing **`void`** makes the parser ignore a column. In the initial example **`void`** could be given as the second template parameter to ignore the second (age) column in the CSV, a tuple of only 2 parameters would be retuned:
|
||||||
```cpp
|
```cpp
|
||||||
// returns std::tuple<std::string, float>
|
// returns std::tuple<std::string, float>
|
||||||
auto [name, grade] = p.get_next<std::string, void, float>();
|
auto [id, grade] = p.get_next<std::string, void, float>();
|
||||||
```
|
```
|
||||||
Works with different types of conversions too:
|
Works with different types of conversions too:
|
||||||
```cpp
|
```cpp
|
||||||
using student = std::tuple<std::string, void, float>;
|
using student = std::tuple<std::string, void, float>;
|
||||||
|
|
||||||
// returns std::tuple<std::string, float>
|
// returns std::tuple<std::string, float>
|
||||||
auto [name, grade] = p.get_next<student>();
|
auto [id, grade] = p.get_next<student>();
|
||||||
```
|
```
|
||||||
Values can also be converted to **`std::string_view`**. It is more efficient then converting values to **`std::string`** but one must be careful with the lifetime of it.
|
Values can also be converted to **`std::string_view`**. It is more efficient then converting values to **`std::string`** but one must be careful with the lifetime of it.
|
||||||
```cpp
|
```cpp
|
||||||
// string_view name stays valid until the next line is read
|
// string_view id stays valid until the next line is read
|
||||||
auto [name, age, grade] = p.get_next<std::string_view, int, float>();
|
auto [id, age, grade] = p.get_next<std::string_view, int, float>();
|
||||||
```
|
```
|
||||||
|
|
||||||
To ignore a whole row, **`ignore_next`** could be used, returns **`false`** if **`eof`**:
|
To ignore a whole row, **`ignore_next`** could be used, returns **`false`** if **`eof`**:
|
||||||
@ -360,30 +405,36 @@ bool parser::ignore_next();
|
|||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
// returns std::tuple<std::string, int, std::optional<float>>
|
// returns std::tuple<std::string, int, std::optional<float>>
|
||||||
auto [name, age, grade] = p.get_next<std::string, int, std::optional<float>();
|
auto [id, age, grade] = p.get_next<std::string, int, std::optional<float>>();
|
||||||
if(grade) {
|
if (grade) {
|
||||||
// do something with grade
|
std::cout << grade.value() << std::endl;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
Similar to **`std::optional`**, **`std::variant`** could be used to try other conversions if the previous failed _(Note, conversion to std::string will always pass)_:
|
Similar to **`std::optional`**, **`std::variant`** could be used to try other conversions if the previous failed _(Note, conversion to std::string will always pass)_:
|
||||||
```cpp
|
```cpp
|
||||||
// returns std::tuple<std::string, int, std::variant<float, char>>
|
// returns std::tuple<std::string, int, std::variant<float, char>>
|
||||||
auto [name, age, grade] =
|
auto [id, age, grade] =
|
||||||
p.get_next<std::string, int, std::variant<float, char>();
|
p.get_next<std::string, int, std::variant<float, char>>();
|
||||||
if(std::holds_alternative<float>(grade)) {
|
if (std::holds_alternative<float>(grade)) {
|
||||||
// grade set as float
|
// grade set as float
|
||||||
} else if(std::holds_alternative<char>(grade)) {
|
} else if (std::holds_alternative<char>(grade)) {
|
||||||
// grade set as char
|
// grade set as char
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
Passing **`char`** and types that are aliases to it such as **`uint8_t`** and **`int8_t`** make the parser interpret the input data as a single character in a similar way to how **`std::cin`** does it. To read numeric values into something like **`uint8_t`** the **`ss::uint8`** and **`ss::int8`** types can be used. These are wrappers around the corresponding char aliases and can be implicitly converted to and from them. When these types are given to the parser he will try to read the given data and store it in the underlying element, but this time as a numeric value instead of a single character.
|
||||||
|
```cpp
|
||||||
|
// returns std::tuple<std::string, ss::uint8, float>
|
||||||
|
auto [id, age, grade] = p.get_next<std::string, ss::uint8, float>();
|
||||||
|
uint8_t age_copy = age;
|
||||||
|
```
|
||||||
## Restrictions
|
## Restrictions
|
||||||
|
|
||||||
Custom **`restrictions`** can be used to narrow down the conversions of unwanted values. **`ss::ir`** (in range) and **`ss::ne`** (none empty) are some of those:
|
Custom **`restrictions`** can be used to narrow down the conversions of unwanted values. **`ss::ir`** (in range) and **`ss::ne`** (none empty) are some of those:
|
||||||
```cpp
|
```cpp
|
||||||
// ss::ne makes sure that the name is not empty
|
// ss::ne makes sure that the id is not empty
|
||||||
// ss::ir makes sure that the grade will be in range [0, 10]
|
// ss::ir makes sure that the grade will be in range [0, 10]
|
||||||
// returns std::tuple<std::string, int, float>
|
// returns std::tuple<std::string, int, float>
|
||||||
auto [name, age, grade] =
|
auto [id, age, grade] =
|
||||||
p.get_next<ss::ne<std::string>, int, ss::ir<float, 0, 10>>();
|
p.get_next<ss::ne<std::string>, int, ss::ir<float, 0, 10>>();
|
||||||
```
|
```
|
||||||
If the restrictions are not met, the conversion will fail. Other predefined restrictions are **`ss::ax`** (all except), **`ss::nx`** (none except) and **`ss::oor`** (out of range), **`ss::lt`** (less than), ...(see *restrictions.hpp*):
|
If the restrictions are not met, the conversion will fail. Other predefined restrictions are **`ss::ax`** (all except), **`ss::nx`** (none except) and **`ss::oor`** (out of range), **`ss::lt`** (less than), ...(see *restrictions.hpp*):
|
||||||
@ -410,9 +461,11 @@ struct even {
|
|||||||
};
|
};
|
||||||
```
|
```
|
||||||
```cpp
|
```cpp
|
||||||
// only even numbers will pass
|
// ...
|
||||||
|
// only even numbers will pass without invoking error handling
|
||||||
// returns std::tuple<std::string, int>
|
// returns std::tuple<std::string, int>
|
||||||
auto [name, age] = p.get_next<std::string, even<int>, void>();
|
const auto& [id, age] = p.get_next<std::string, even<int>, void>();
|
||||||
|
// ...
|
||||||
```
|
```
|
||||||
## Custom conversions
|
## Custom conversions
|
||||||
|
|
||||||
@ -439,21 +492,35 @@ The shape enum will be used in an example below. The **`inline`** is there just
|
|||||||
|
|
||||||
## Error handling
|
## Error handling
|
||||||
|
|
||||||
|
By default, the parser handles errors only using the **`valid`** method which would return **`false`** if the file could not be opened, or if the conversion could not be made (invalid types, invalid number of columns, ...).\
|
||||||
|
The **`eof`** method can be used to detect if the end of the file was reached.
|
||||||
|
|
||||||
Detailed error messages can be accessed via the **`error_msg`** method, and to enable them **`ss::string_error`** needs to be included in the setup. If **`ss::string_error`** is not defined, the **`error_msg`** method will not be defined either.
|
Detailed error messages can be accessed via the **`error_msg`** method, and to enable them **`ss::string_error`** needs to be included in the setup. If **`ss::string_error`** is not defined, the **`error_msg`** method will not be defined either.
|
||||||
|
|
||||||
|
The line number can be fetched using the **`line`** method.
|
||||||
|
The cursor position can be fetched using the **`position`** method.
|
||||||
```cpp
|
```cpp
|
||||||
const std::string& parser::error_msg();
|
const std::string& parser::error_msg() const;
|
||||||
bool parser::valid();
|
bool parser::valid() const;
|
||||||
bool parser::eof();
|
bool parser::eof() const;
|
||||||
|
size_t parser::line() const;
|
||||||
|
size_t parser::position() const;
|
||||||
|
|
||||||
// ...
|
// ...
|
||||||
ss::parser<ss::string_error> parser;
|
ss::parser<ss::string_error> parser;
|
||||||
```
|
```
|
||||||
An error can be detected using the **`valid`** method which would return **`false`** if the file could not be opened, or if the conversion could not be made (invalid types, invalid number of columns, ...). The **`eof`** method can be used to detect if the end of the file was reached.
|
|
||||||
|
The above two methods are preferable if invalid inputs are expected and allows for fast handling, but the parser can also be forced to throw an exception in case of an invalid input using the **`ss::throw_on_error`** setup option. Enabling exceptions also makes the **`valid`** method always return **`true`**.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
ss::parser<ss::throw_on_error> parser;
|
||||||
|
```
|
||||||
|
*Note, enabling this option will also make the parser throw if the constructor fails.*
|
||||||
|
|
||||||
## Substitute conversions
|
## Substitute conversions
|
||||||
|
|
||||||
The parser can also be used to effectively parse files whose rows are not always in the same format (not a classical csv but still csv-like). A more complicated example would be the best way to demonstrate such a scenario.
|
The parser can also be used to effectively parse files whose rows are not always in the same format (not a classical CSV but still CSV-like). A more complicated example would be the best way to demonstrate such a scenario.\
|
||||||
|
***Important, substitute conversions do not work when throw_on_error is enabled.***
|
||||||
|
|
||||||
Supposing we have a file containing different shapes in given formats:
|
Supposing we have a file containing different shapes in given formats:
|
||||||
* circle RADIUS
|
* circle RADIUS
|
||||||
@ -482,11 +549,16 @@ while (!p.eof()) {
|
|||||||
using udbl = ss::gte<double, 0>;
|
using udbl = ss::gte<double, 0>;
|
||||||
|
|
||||||
auto [circle_or_square, rectangle, triangle] =
|
auto [circle_or_square, rectangle, triangle] =
|
||||||
p.try_next<ss::nx<shape, shape::circle, shape::square>, udbl>()
|
p.try_next<ss::nx<shape, shape::circle, shape::square>, udbl>()
|
||||||
.or_else<ss::nx<shape, shape::rectangle>, udbl, udbl>()
|
.or_else<ss::nx<shape, shape::rectangle>, udbl, udbl>()
|
||||||
.or_else<ss::nx<shape, shape::triangle>, udbl, udbl, udbl>()
|
.or_else<ss::nx<shape, shape::triangle>, udbl, udbl, udbl>()
|
||||||
.values();
|
.values();
|
||||||
|
|
||||||
|
if (!p.valid()) {
|
||||||
|
// handle error
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (circle_or_square) {
|
if (circle_or_square) {
|
||||||
auto& [s, x] = circle_or_square.value();
|
auto& [s, x] = circle_or_square.value();
|
||||||
double area = (s == shape::circle) ? x * x * M_PI : x * x;
|
double area = (s == shape::circle) ? x * x * M_PI : x * x;
|
||||||
@ -530,31 +602,34 @@ Each of those **`composite`** conversions can accept a lambda (or anything calla
|
|||||||
// non negative double
|
// non negative double
|
||||||
using udbl = ss::gte<double, 0>;
|
using udbl = ss::gte<double, 0>;
|
||||||
|
|
||||||
p.try_next<ss::nx<shape, shape::circle, shape::square>, udbl>(
|
while (!p.eof()) {
|
||||||
[&](const auto& data) {
|
p.try_next<ss::nx<shape, shape::circle, shape::square>, udbl>(
|
||||||
const auto& [s, x] = data;
|
[&](const auto& data) {
|
||||||
double area = (s == shape::circle) ? x * x * M_PI : x * x;
|
const auto& [s, x] = data;
|
||||||
shapes.emplace_back(s, area);
|
double area = (s == shape::circle) ? x * x * M_PI : x * x;
|
||||||
})
|
shapes.emplace_back(s, area);
|
||||||
.or_else<ss::nx<shape, shape::rectangle>, udbl, udbl>(
|
})
|
||||||
[&](const shape s, const double a, const double b) {
|
.or_else<ss::nx<shape, shape::rectangle>, udbl, udbl>(
|
||||||
shapes.emplace_back(s, a * b);
|
[&](shape s, double a, double b) { shapes.emplace_back(s, a * b); })
|
||||||
})
|
.or_else<ss::nx<shape, shape::triangle>, udbl, udbl, udbl>(
|
||||||
.or_else<ss::nx<shape, shape::triangle>, udbl, udbl, udbl>(
|
[&](auto s, auto a, auto b, auto c) {
|
||||||
[&](auto&& s, auto& a, const double& b, double& c) {
|
double sh = (a + b + c) / 2;
|
||||||
double sh = (a + b + c) / 2;
|
if (sh >= a && sh >= b && sh >= c) {
|
||||||
if (sh >= a && sh >= b && sh >= c) {
|
double area = sqrt(sh * (sh - a) * (sh - b) * (sh - c));
|
||||||
double area = sqrt(sh * (sh - a) * (sh - b) * (sh - c));
|
shapes.emplace_back(s, area);
|
||||||
shapes.emplace_back(s, area);
|
}
|
||||||
}
|
})
|
||||||
|
.on_error([] {
|
||||||
|
// handle error
|
||||||
});
|
});
|
||||||
|
}
|
||||||
```
|
```
|
||||||
It is a bit less readable, but it removes the need to check which conversion was invoked. The **`composite`** also has an **`on_error`** method which accepts a lambda which will be invoked if no previous conversions were successful. The lambda can take no arguments or just one argument, an **`std::string`**, in which the error message is stored if **`string_error`** is enabled:
|
It is a bit less readable, but it removes the need to check which conversion was invoked. The **`composite`** also has an **`on_error`** method which accepts a lambda which will be invoked if no previous conversions were successful. The lambda can take no arguments or just one argument, an **`std::string`**, in which the error message is stored if **`string_error`** is enabled:
|
||||||
```cpp
|
```cpp
|
||||||
p.try_next<int>()
|
p.try_next<int>()
|
||||||
.on_error([](const std::string& e) { /* int conversion failed */ })
|
.on_error([](const std::string& e) { /* int conversion failed */ })
|
||||||
.or_object<x, double>()
|
.or_object<x, double>()
|
||||||
.on_error([] { /* int and x (all) conversions failed */ });
|
.on_error([] { /* int and x conversions failed (all previous failed) */ });
|
||||||
```
|
```
|
||||||
*See unit tests for more examples.*
|
*See unit tests for more examples.*
|
||||||
|
|
||||||
@ -583,7 +658,7 @@ if (c.valid()) {
|
|||||||
All setup parameters, special types and restrictions work on the converter too.
|
All setup parameters, special types and restrictions work on the converter too.
|
||||||
Error handling is also identical to error handling of the parser.
|
Error handling is also identical to error handling of the parser.
|
||||||
|
|
||||||
The converter has also the ability to just split the line, ~~tho it does not change it (kinda statically), hence the name of the library~~ and depending if either quoting or escaping are enabled it may change the line, rather than creating a copy, for performance reasons (the name of the library does not apply anymore, I may change it). It returns an **`std::vector`** of **`std::pair`**s of pointers, begin and end, each pair representing a split segment (column) of the whole string. The vector can then be used in a overloaded **`convert`** method. This allows the reuse of the same line without splitting it on every conversion.
|
The converter has also the ability to just split the line, and depending if either quoting or escaping are enabled it may change the line, rather than creating a copy, for performance reasons. It returns an **`std::vector`** of **`std::pair`**s of pointers, begin and end, each pair representing a split segment (column) of the whole string. The vector can then be used in a overloaded **`convert`** method. This allows the reuse of the same line without splitting it on every conversion.
|
||||||
```cpp
|
```cpp
|
||||||
ss::converter c;
|
ss::converter c;
|
||||||
auto split_line = c.split("circle 10", " ");
|
auto split_line = c.split("circle 10", " ");
|
||||||
|
@ -1,9 +1,15 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <cstdint>
|
#include <cerrno>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#if !__unix__
|
||||||
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace ss {
|
namespace ss {
|
||||||
|
|
||||||
struct none {};
|
struct none {};
|
||||||
@ -12,67 +18,134 @@ using string_range = std::pair<const char*, const char*>;
|
|||||||
using split_data = std::vector<string_range>;
|
using split_data = std::vector<string_range>;
|
||||||
|
|
||||||
constexpr inline auto default_delimiter = ",";
|
constexpr inline auto default_delimiter = ",";
|
||||||
|
constexpr inline auto get_line_initial_buffer_size = 128;
|
||||||
|
|
||||||
template <bool StringError>
|
template <bool StringError>
|
||||||
inline void assert_string_error_defined() {
|
void assert_string_error_defined() {
|
||||||
static_assert(StringError,
|
static_assert(StringError,
|
||||||
"'string_error' needs to be enabled to use 'error_msg'");
|
"'string_error' needs to be enabled to use 'error_msg'");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <bool ThrowOnError>
|
||||||
|
void assert_throw_on_error_not_defined() {
|
||||||
|
static_assert(!ThrowOnError, "cannot handle errors manually if "
|
||||||
|
"'throw_on_error' is enabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline void* strict_realloc(void* ptr, size_t size) {
|
||||||
|
ptr = std::realloc(ptr, size);
|
||||||
|
if (!ptr) {
|
||||||
|
throw std::bad_alloc{};
|
||||||
|
}
|
||||||
|
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
#if __unix__
|
#if __unix__
|
||||||
inline ssize_t get_line(char** lineptr, size_t* n, FILE* stream) {
|
[[nodiscard]] inline ssize_t get_line_file(char*& lineptr, size_t& n,
|
||||||
return getline(lineptr, n, stream);
|
FILE* file) {
|
||||||
|
return getline(&lineptr, &n, file);
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
using ssize_t = int64_t;
|
|
||||||
inline ssize_t get_line(char** lineptr, size_t* n, FILE* stream) {
|
|
||||||
size_t pos;
|
|
||||||
int c;
|
|
||||||
|
|
||||||
if (lineptr == nullptr || stream == nullptr || n == nullptr) {
|
using ssize_t = intptr_t;
|
||||||
errno = EINVAL;
|
|
||||||
return -1;
|
[[nodiscard]] inline ssize_t get_line_file(char*& lineptr, size_t& n,
|
||||||
|
FILE* file) {
|
||||||
|
std::array<char, get_line_initial_buffer_size> buff;
|
||||||
|
|
||||||
|
if (lineptr == nullptr || n < sizeof(buff)) {
|
||||||
|
const size_t new_n = sizeof(buff);
|
||||||
|
lineptr = static_cast<char*>(strict_realloc(lineptr, new_n));
|
||||||
|
n = new_n;
|
||||||
}
|
}
|
||||||
|
|
||||||
c = getc(stream);
|
lineptr[0] = '\0';
|
||||||
if (c == EOF) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (*lineptr == nullptr) {
|
size_t line_used = 0;
|
||||||
*lineptr = static_cast<char*>(malloc(128));
|
while (std::fgets(buff.data(), sizeof(buff), file) != nullptr) {
|
||||||
if (*lineptr == nullptr) {
|
line_used = std::strlen(lineptr);
|
||||||
return -1;
|
size_t buff_used = std::strlen(buff.data());
|
||||||
}
|
|
||||||
*n = 128;
|
|
||||||
}
|
|
||||||
|
|
||||||
pos = 0;
|
if (n <= buff_used + line_used) {
|
||||||
while (c != EOF) {
|
const size_t new_n = n * 2;
|
||||||
if (pos + 1 >= *n) {
|
lineptr = static_cast<char*>(strict_realloc(lineptr, new_n));
|
||||||
size_t new_size = *n + (*n >> 2);
|
n = new_n;
|
||||||
if (new_size < 128) {
|
|
||||||
new_size = 128;
|
|
||||||
}
|
|
||||||
char* new_ptr = static_cast<char*>(
|
|
||||||
realloc(static_cast<void*>(*lineptr), new_size));
|
|
||||||
if (new_ptr == nullptr) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
*n = new_size;
|
|
||||||
*lineptr = new_ptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
(*lineptr)[pos++] = c;
|
std::memcpy(lineptr + line_used, buff.data(), buff_used);
|
||||||
if (c == '\n') {
|
line_used += buff_used;
|
||||||
break;
|
lineptr[line_used] = '\0';
|
||||||
|
|
||||||
|
if (lineptr[line_used - 1] == '\n') {
|
||||||
|
return line_used;
|
||||||
}
|
}
|
||||||
c = getc(stream);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
(*lineptr)[pos] = '\0';
|
return (line_used != 0) ? line_used : -1;
|
||||||
return pos;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
} /* ss */
|
[[nodiscard]] inline ssize_t get_line_buffer(char*& lineptr, size_t& n,
|
||||||
|
const char* const csv_data_buffer,
|
||||||
|
size_t csv_data_size,
|
||||||
|
size_t& curr_char) {
|
||||||
|
if (curr_char >= csv_data_size) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lineptr == nullptr || n < get_line_initial_buffer_size) {
|
||||||
|
auto* new_lineptr = static_cast<char*>(
|
||||||
|
strict_realloc(lineptr, get_line_initial_buffer_size));
|
||||||
|
lineptr = new_lineptr;
|
||||||
|
n = get_line_initial_buffer_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t line_used = 0;
|
||||||
|
while (curr_char < csv_data_size) {
|
||||||
|
if (line_used + 1 >= n) {
|
||||||
|
const size_t new_n = n * 2;
|
||||||
|
|
||||||
|
char* new_lineptr =
|
||||||
|
static_cast<char*>(strict_realloc(lineptr, new_n));
|
||||||
|
n = new_n;
|
||||||
|
lineptr = new_lineptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto c = csv_data_buffer[curr_char++];
|
||||||
|
lineptr[line_used++] = c;
|
||||||
|
if (c == '\n') {
|
||||||
|
lineptr[line_used] = '\0';
|
||||||
|
return line_used;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lineptr[line_used] = '\0';
|
||||||
|
return line_used;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::tuple<ssize_t, bool> get_line(
|
||||||
|
char*& buffer, size_t& buffer_size, FILE* file,
|
||||||
|
const char* const csv_data_buffer, size_t csv_data_size,
|
||||||
|
size_t& curr_char) {
|
||||||
|
ssize_t ssize = 0;
|
||||||
|
if (file) {
|
||||||
|
ssize = get_line_file(buffer, buffer_size, file);
|
||||||
|
curr_char += ssize;
|
||||||
|
} else {
|
||||||
|
ssize = get_line_buffer(buffer, buffer_size, csv_data_buffer,
|
||||||
|
csv_data_size, curr_char);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ssize == -1) {
|
||||||
|
if (errno == ENOMEM) {
|
||||||
|
throw std::bad_alloc{};
|
||||||
|
}
|
||||||
|
return {ssize, true};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {ssize, false};
|
||||||
|
}
|
||||||
|
|
||||||
|
} /* namespace ss */
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include "exception.hpp"
|
||||||
#include "extract.hpp"
|
#include "extract.hpp"
|
||||||
#include "function_traits.hpp"
|
#include "function_traits.hpp"
|
||||||
#include "restrictions.hpp"
|
#include "restrictions.hpp"
|
||||||
@ -95,11 +96,12 @@ constexpr bool tied_class_v = tied_class<Ts...>::value;
|
|||||||
// converter
|
// converter
|
||||||
////////////////
|
////////////////
|
||||||
|
|
||||||
template <typename... Matchers>
|
template <typename... Options>
|
||||||
class converter {
|
class converter {
|
||||||
using line_ptr_type = typename splitter<Matchers...>::line_ptr_type;
|
using line_ptr_type = typename splitter<Options...>::line_ptr_type;
|
||||||
|
|
||||||
constexpr static auto string_error = setup<Matchers...>::string_error;
|
constexpr static auto string_error = setup<Options...>::string_error;
|
||||||
|
constexpr static auto throw_on_error = setup<Options...>::throw_on_error;
|
||||||
constexpr static auto default_delimiter = ",";
|
constexpr static auto default_delimiter = ",";
|
||||||
|
|
||||||
using error_type = std::conditional_t<string_error, std::string, bool>;
|
using error_type = std::conditional_t<string_error, std::string, bool>;
|
||||||
@ -108,29 +110,34 @@ public:
|
|||||||
// parses line with given delimiter, returns a 'T' object created with
|
// parses line with given delimiter, returns a 'T' object created with
|
||||||
// extracted values of type 'Ts'
|
// extracted values of type 'Ts'
|
||||||
template <typename T, typename... Ts>
|
template <typename T, typename... Ts>
|
||||||
T convert_object(line_ptr_type line,
|
[[nodiscard]] T convert_object(
|
||||||
const std::string& delim = default_delimiter) {
|
line_ptr_type line, const std::string& delim = default_delimiter) {
|
||||||
return to_object<T>(convert<Ts...>(line, delim));
|
return to_object<T>(convert<Ts...>(line, delim));
|
||||||
}
|
}
|
||||||
|
|
||||||
// parses line with given delimiter, returns tuple of objects with
|
// parses line with given delimiter, returns tuple of objects with
|
||||||
// extracted values of type 'Ts'
|
// extracted values of type 'Ts'
|
||||||
template <typename... Ts>
|
template <typename... Ts>
|
||||||
no_void_validator_tup_t<Ts...> convert(
|
[[nodiscard]] no_void_validator_tup_t<Ts...> convert(
|
||||||
line_ptr_type line, const std::string& delim = default_delimiter) {
|
line_ptr_type line, const std::string& delim = default_delimiter) {
|
||||||
split(line, delim);
|
split(line, delim);
|
||||||
return convert<Ts...>(splitter_.split_data_);
|
if (splitter_.valid()) {
|
||||||
|
return convert<Ts...>(splitter_.get_split_data());
|
||||||
|
} else {
|
||||||
|
handle_error_bad_split();
|
||||||
|
return {};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// parses already split line, returns 'T' object with extracted values
|
// parses already split line, returns 'T' object with extracted values
|
||||||
template <typename T, typename... Ts>
|
template <typename T, typename... Ts>
|
||||||
T convert_object(const split_data& elems) {
|
[[nodiscard]] T convert_object(const split_data& elems) {
|
||||||
return to_object<T>(convert<Ts...>(elems));
|
return to_object<T>(convert<Ts...>(elems));
|
||||||
}
|
}
|
||||||
|
|
||||||
// same as above, but uses cached split line
|
// same as above, but uses cached split line
|
||||||
template <typename T, typename... Ts>
|
template <typename T, typename... Ts>
|
||||||
T convert_object() {
|
[[nodiscard]] T convert_object() {
|
||||||
return to_object<T>(convert<Ts...>());
|
return to_object<T>(convert<Ts...>());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,11 +146,12 @@ public:
|
|||||||
// one argument is given which is a class which has a tied
|
// one argument is given which is a class which has a tied
|
||||||
// method which returns a tuple, returns that type
|
// method which returns a tuple, returns that type
|
||||||
template <typename T, typename... Ts>
|
template <typename T, typename... Ts>
|
||||||
no_void_validator_tup_t<T, Ts...> convert(const split_data& elems) {
|
[[nodiscard]] no_void_validator_tup_t<T, Ts...> convert(
|
||||||
|
const split_data& elems) {
|
||||||
if constexpr (sizeof...(Ts) == 0 && is_instance_of_v<std::tuple, T>) {
|
if constexpr (sizeof...(Ts) == 0 && is_instance_of_v<std::tuple, T>) {
|
||||||
return convert_impl(elems, static_cast<T*>(nullptr));
|
return convert_impl(elems, static_cast<T*>(nullptr));
|
||||||
} else if constexpr (tied_class_v<T, Ts...>) {
|
} else if constexpr (tied_class_v<T, Ts...>) {
|
||||||
using arg_ref_tuple = std::result_of_t<decltype (&T::tied)(T)>;
|
using arg_ref_tuple = std::invoke_result_t<decltype(&T::tied), T>;
|
||||||
using arg_tuple = apply_trait_t<std::decay, arg_ref_tuple>;
|
using arg_tuple = apply_trait_t<std::decay, arg_ref_tuple>;
|
||||||
|
|
||||||
return to_object<T>(
|
return to_object<T>(
|
||||||
@ -155,24 +163,26 @@ public:
|
|||||||
|
|
||||||
// same as above, but uses cached split line
|
// same as above, but uses cached split line
|
||||||
template <typename T, typename... Ts>
|
template <typename T, typename... Ts>
|
||||||
no_void_validator_tup_t<T, Ts...> convert() {
|
[[nodiscard]] no_void_validator_tup_t<T, Ts...> convert() {
|
||||||
return convert<T, Ts...>(splitter_.split_data_);
|
return convert<T, Ts...>(splitter_.get_split_data());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool valid() const {
|
[[nodiscard]] bool valid() const {
|
||||||
if constexpr (string_error) {
|
if constexpr (string_error) {
|
||||||
return error_.empty();
|
return error_.empty();
|
||||||
|
} else if constexpr (throw_on_error) {
|
||||||
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return !error_;
|
return !error_;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string& error_msg() const {
|
[[nodiscard]] const std::string& error_msg() const {
|
||||||
assert_string_error_defined<string_error>();
|
assert_string_error_defined<string_error>();
|
||||||
return error_;
|
return error_;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool unterminated_quote() const {
|
[[nodiscard]] bool unterminated_quote() const {
|
||||||
return splitter_.unterminated_quote();
|
return splitter_.unterminated_quote();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,9 +190,9 @@ public:
|
|||||||
// contain the beginnings and the ends of each column of the string
|
// contain the beginnings and the ends of each column of the string
|
||||||
const split_data& split(line_ptr_type line,
|
const split_data& split(line_ptr_type line,
|
||||||
const std::string& delim = default_delimiter) {
|
const std::string& delim = default_delimiter) {
|
||||||
splitter_.split_data_.clear();
|
splitter_.clear_split_data();
|
||||||
if (line[0] == '\0') {
|
if (line[0] == '\0') {
|
||||||
return splitter_.split_data_;
|
return splitter_.get_split_data();
|
||||||
}
|
}
|
||||||
|
|
||||||
return splitter_.split(line, delim);
|
return splitter_.split(line, delim);
|
||||||
@ -198,7 +208,7 @@ private:
|
|||||||
return splitter_.resplit(new_line, new_size, delim);
|
return splitter_.resplit(new_line, new_size, delim);
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t size_shifted() {
|
[[nodiscard]] size_t size_shifted() {
|
||||||
return splitter_.size_shifted();
|
return splitter_.size_shifted();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -214,9 +224,11 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string error_sufix(const string_range msg, size_t pos) const {
|
[[nodiscard]] std::string error_sufix(const string_range msg,
|
||||||
|
size_t pos) const {
|
||||||
|
constexpr static auto reserve_size = 32;
|
||||||
std::string error;
|
std::string error;
|
||||||
error.reserve(32);
|
error.reserve(reserve_size);
|
||||||
error.append("at column ")
|
error.append("at column ")
|
||||||
.append(std::to_string(pos + 1))
|
.append(std::to_string(pos + 1))
|
||||||
.append(": \'")
|
.append(": \'")
|
||||||
@ -225,98 +237,112 @@ private:
|
|||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_error_unterminated_quote() {
|
void handle_error_bad_split() {
|
||||||
if constexpr (string_error) {
|
if constexpr (string_error) {
|
||||||
error_.clear();
|
error_.clear();
|
||||||
error_.append(splitter_.error_msg());
|
error_.append(splitter_.error_msg());
|
||||||
} else {
|
} else if constexpr (!throw_on_error) {
|
||||||
error_ = true;
|
error_ = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_error_unterminated_escape() {
|
void handle_error_unterminated_escape() {
|
||||||
if constexpr (string_error) {
|
if constexpr (string_error) {
|
||||||
error_.clear();
|
error_.clear();
|
||||||
splitter_.set_error_unterminated_escape();
|
splitter_.handle_error_unterminated_escape();
|
||||||
error_.append(splitter_.error_msg());
|
error_.append(splitter_.error_msg());
|
||||||
|
} else if constexpr (throw_on_error) {
|
||||||
|
splitter_.handle_error_unterminated_escape();
|
||||||
} else {
|
} else {
|
||||||
error_ = true;
|
error_ = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_error_multiline_limit_reached() {
|
void handle_error_unterminated_quote() {
|
||||||
if constexpr (string_error) {
|
if constexpr (string_error) {
|
||||||
error_.clear();
|
error_.clear();
|
||||||
error_.append("multiline limit reached.");
|
splitter_.handle_error_unterminated_quote();
|
||||||
|
error_.append(splitter_.error_msg());
|
||||||
|
} else if constexpr (throw_on_error) {
|
||||||
|
splitter_.handle_error_unterminated_quote();
|
||||||
} else {
|
} else {
|
||||||
error_ = true;
|
error_ = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_error_invalid_conversion(const string_range msg, size_t pos) {
|
void handle_error_multiline_limit_reached() {
|
||||||
|
constexpr static auto error_msg = "multiline limit reached";
|
||||||
|
splitter_.unterminated_quote_ = false;
|
||||||
|
|
||||||
if constexpr (string_error) {
|
if constexpr (string_error) {
|
||||||
error_.clear();
|
error_.clear();
|
||||||
error_.append("invalid conversion for parameter ")
|
error_.append(error_msg);
|
||||||
.append(error_sufix(msg, pos));
|
} else if constexpr (throw_on_error) {
|
||||||
|
throw ss::exception{error_msg};
|
||||||
} else {
|
} else {
|
||||||
error_ = true;
|
error_ = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_error_validate(const char* const error, const string_range msg,
|
void handle_error_invalid_conversion(const string_range msg, size_t pos) {
|
||||||
size_t pos) {
|
constexpr static auto error_msg = "invalid conversion for parameter ";
|
||||||
|
|
||||||
|
if constexpr (string_error) {
|
||||||
|
error_.clear();
|
||||||
|
error_.append(error_msg).append(error_sufix(msg, pos));
|
||||||
|
} else if constexpr (throw_on_error) {
|
||||||
|
throw ss::exception{error_msg + error_sufix(msg, pos)};
|
||||||
|
} else {
|
||||||
|
error_ = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void handle_error_validation_failed(const char* const error,
|
||||||
|
const string_range msg, size_t pos) {
|
||||||
if constexpr (string_error) {
|
if constexpr (string_error) {
|
||||||
error_.clear();
|
error_.clear();
|
||||||
error_.append(error).append(" ").append(error_sufix(msg, pos));
|
error_.append(error).append(" ").append(error_sufix(msg, pos));
|
||||||
|
} else if constexpr (throw_on_error) {
|
||||||
|
throw ss::exception{error + (" " + error_sufix(msg, pos))};
|
||||||
} else {
|
} else {
|
||||||
error_ = true;
|
error_ = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_error_number_of_columns(size_t expected_pos, size_t pos) {
|
void handle_error_number_of_columns(size_t expected_pos, size_t pos) {
|
||||||
|
constexpr static auto error_msg1 =
|
||||||
|
"invalid number of columns, expected: ";
|
||||||
|
constexpr static auto error_msg2 = ", got: ";
|
||||||
|
|
||||||
if constexpr (string_error) {
|
if constexpr (string_error) {
|
||||||
error_.clear();
|
error_.clear();
|
||||||
error_.append("invalid number of columns, expected: ")
|
error_.append(error_msg1)
|
||||||
.append(std::to_string(expected_pos))
|
.append(std::to_string(expected_pos))
|
||||||
.append(", got: ")
|
.append(error_msg2)
|
||||||
.append(std::to_string(pos));
|
.append(std::to_string(pos));
|
||||||
|
} else if constexpr (throw_on_error) {
|
||||||
|
throw ss::exception{error_msg1 + std::to_string(expected_pos) +
|
||||||
|
error_msg2 + std::to_string(pos)};
|
||||||
} else {
|
} else {
|
||||||
error_ = true;
|
error_ = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_error_incompatible_mapping(size_t argument_size,
|
void handle_error_incompatible_mapping(size_t argument_size,
|
||||||
size_t mapping_size) {
|
size_t mapping_size) {
|
||||||
|
constexpr static auto error_msg1 =
|
||||||
|
"number of arguments does not match mapping, expected: ";
|
||||||
|
constexpr static auto error_msg2 = ", got: ";
|
||||||
|
|
||||||
if constexpr (string_error) {
|
if constexpr (string_error) {
|
||||||
error_.clear();
|
error_.clear();
|
||||||
error_
|
error_.append(error_msg1)
|
||||||
.append(
|
|
||||||
"number of arguments does not match mapping, expected: ")
|
|
||||||
.append(std::to_string(mapping_size))
|
.append(std::to_string(mapping_size))
|
||||||
.append(", got: ")
|
.append(error_msg2)
|
||||||
.append(std::to_string(argument_size));
|
.append(std::to_string(argument_size));
|
||||||
} else {
|
} else if constexpr (throw_on_error) {
|
||||||
error_ = true;
|
throw ss::exception{error_msg1 + std::to_string(mapping_size) +
|
||||||
}
|
error_msg2 + std::to_string(argument_size)};
|
||||||
}
|
|
||||||
|
|
||||||
void set_error_invalid_mapping() {
|
|
||||||
if constexpr (string_error) {
|
|
||||||
error_.clear();
|
|
||||||
error_.append("received empty mapping");
|
|
||||||
} else {
|
|
||||||
error_ = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_error_mapping_out_of_range(size_t maximum_index,
|
|
||||||
size_t number_of_columnts) {
|
|
||||||
if constexpr (string_error) {
|
|
||||||
error_.clear();
|
|
||||||
error_.append("maximum index: ")
|
|
||||||
.append(std::to_string(maximum_index))
|
|
||||||
.append(", greater then number of columns: ")
|
|
||||||
.append(std::to_string(number_of_columnts));
|
|
||||||
} else {
|
} else {
|
||||||
error_ = true;
|
error_ = true;
|
||||||
}
|
}
|
||||||
@ -327,40 +353,39 @@ private:
|
|||||||
////////////////
|
////////////////
|
||||||
|
|
||||||
template <typename... Ts>
|
template <typename... Ts>
|
||||||
no_void_validator_tup_t<Ts...> convert_impl(const split_data& elems) {
|
[[nodiscard]] no_void_validator_tup_t<Ts...> convert_impl(
|
||||||
|
const split_data& elems) {
|
||||||
clear_error();
|
clear_error();
|
||||||
using return_type = no_void_validator_tup_t<Ts...>;
|
|
||||||
|
|
||||||
if (!splitter_.valid()) {
|
if (!splitter_.valid()) {
|
||||||
set_error_unterminated_quote();
|
handle_error_bad_split();
|
||||||
no_void_validator_tup_t<Ts...> ret{};
|
return {};
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!columns_mapped()) {
|
if (!columns_mapped()) {
|
||||||
if (sizeof...(Ts) != elems.size()) {
|
if (sizeof...(Ts) != elems.size()) {
|
||||||
set_error_number_of_columns(sizeof...(Ts), elems.size());
|
handle_error_number_of_columns(sizeof...(Ts), elems.size());
|
||||||
return return_type{};
|
return {};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (sizeof...(Ts) != column_mappings_.size()) {
|
if (sizeof...(Ts) != column_mappings_.size()) {
|
||||||
set_error_incompatible_mapping(sizeof...(Ts),
|
handle_error_incompatible_mapping(sizeof...(Ts),
|
||||||
column_mappings_.size());
|
column_mappings_.size());
|
||||||
return return_type{};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (elems.size() != number_of_columns_) {
|
if (elems.size() != number_of_columns_) {
|
||||||
set_error_number_of_columns(number_of_columns_, elems.size());
|
handle_error_number_of_columns(number_of_columns_,
|
||||||
return return_type{};
|
elems.size());
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return extract_tuple<Ts...>(elems);
|
return extract_tuple<Ts...>(elems);
|
||||||
}
|
}
|
||||||
|
|
||||||
// do not know how to specialize by return type :(
|
|
||||||
template <typename... Ts>
|
template <typename... Ts>
|
||||||
no_void_validator_tup_t<std::tuple<Ts...>> convert_impl(
|
[[nodiscard]] no_void_validator_tup_t<std::tuple<Ts...>> convert_impl(
|
||||||
const split_data& elems, const std::tuple<Ts...>*) {
|
const split_data& elems, const std::tuple<Ts...>*) {
|
||||||
return convert_impl<Ts...>(elems);
|
return convert_impl<Ts...>(elems);
|
||||||
}
|
}
|
||||||
@ -369,31 +394,21 @@ private:
|
|||||||
// column mapping
|
// column mapping
|
||||||
////////////////
|
////////////////
|
||||||
|
|
||||||
bool columns_mapped() const {
|
[[nodiscard]] bool columns_mapped() const {
|
||||||
return column_mappings_.size() != 0;
|
return !column_mappings_.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t column_position(size_t tuple_position) const {
|
[[nodiscard]] size_t column_position(size_t tuple_position) const {
|
||||||
if (!columns_mapped()) {
|
if (!columns_mapped()) {
|
||||||
return tuple_position;
|
return tuple_position;
|
||||||
}
|
}
|
||||||
return column_mappings_[tuple_position];
|
return column_mappings_[tuple_position];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// assumes positions are valid and the vector is not empty
|
||||||
void set_column_mapping(std::vector<size_t> positions,
|
void set_column_mapping(std::vector<size_t> positions,
|
||||||
size_t number_of_columns) {
|
size_t number_of_columns) {
|
||||||
if (positions.empty()) {
|
column_mappings_ = std::move(positions);
|
||||||
set_error_invalid_mapping();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto max_index = *std::max_element(positions.begin(), positions.end());
|
|
||||||
if (max_index >= number_of_columns) {
|
|
||||||
set_error_mapping_out_of_range(max_index, number_of_columns);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
column_mappings_ = positions;
|
|
||||||
number_of_columns_ = number_of_columns;
|
number_of_columns_ = number_of_columns;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -414,21 +429,22 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if constexpr (std::is_same_v<T, std::string>) {
|
if constexpr (std::is_same_v<T, std::string>) {
|
||||||
extract(msg.first, msg.second, dst);
|
static_cast<void>(extract(msg.first, msg.second, dst));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!extract(msg.first, msg.second, dst)) {
|
if (!extract(msg.first, msg.second, dst)) {
|
||||||
set_error_invalid_conversion(msg, pos);
|
handle_error_invalid_conversion(msg, pos);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if constexpr (has_m_ss_valid_t<T>) {
|
if constexpr (has_m_ss_valid_t<T>) {
|
||||||
if (T validator; !validator.ss_valid(dst)) {
|
if (T validator; !validator.ss_valid(dst)) {
|
||||||
if constexpr (has_m_error_t<T>) {
|
if constexpr (has_m_error_t<T>) {
|
||||||
set_error_validate(validator.error(), msg, pos);
|
handle_error_validation_failed(validator.error(), msg, pos);
|
||||||
} else {
|
} else {
|
||||||
set_error_validate("validation error", msg, pos);
|
handle_error_validation_failed("validation error", msg,
|
||||||
|
pos);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -459,7 +475,8 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename... Ts>
|
template <typename... Ts>
|
||||||
no_void_validator_tup_t<Ts...> extract_tuple(const split_data& elems) {
|
[[nodiscard]] no_void_validator_tup_t<Ts...> extract_tuple(
|
||||||
|
const split_data& elems) {
|
||||||
static_assert(!all_of_v<std::is_void, Ts...>,
|
static_assert(!all_of_v<std::is_void, Ts...>,
|
||||||
"at least one parameter must be non void");
|
"at least one parameter must be non void");
|
||||||
no_void_validator_tup_t<Ts...> ret{};
|
no_void_validator_tup_t<Ts...> ret{};
|
||||||
@ -472,13 +489,13 @@ private:
|
|||||||
////////////////
|
////////////////
|
||||||
|
|
||||||
error_type error_{};
|
error_type error_{};
|
||||||
splitter<Matchers...> splitter_;
|
splitter<Options...> splitter_;
|
||||||
|
|
||||||
template <typename...>
|
template <typename...>
|
||||||
friend class parser;
|
friend class parser;
|
||||||
|
|
||||||
std::vector<size_t> column_mappings_;
|
std::vector<size_t> column_mappings_;
|
||||||
size_t number_of_columns_;
|
size_t number_of_columns_{0};
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* ss */
|
} /* namespace ss */
|
||||||
|
23
include/ss/exception.hpp
Normal file
23
include/ss/exception.hpp
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <exception>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace ss {
|
||||||
|
|
||||||
|
////////////////
|
||||||
|
// exception
|
||||||
|
////////////////
|
||||||
|
|
||||||
|
class exception : public std::exception {
|
||||||
|
std::string msg_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
exception(std::string msg) : msg_{std::move(msg)} {
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] char const* what() const noexcept override {
|
||||||
|
return msg_.c_str();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} /* namespace ss */
|
@ -1,9 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "type_traits.hpp"
|
#include "type_traits.hpp"
|
||||||
|
#include <charconv>
|
||||||
|
#include <cstdint>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <functional>
|
|
||||||
#include <limits>
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
@ -12,7 +12,9 @@
|
|||||||
#ifndef SSP_DISABLE_FAST_FLOAT
|
#ifndef SSP_DISABLE_FAST_FLOAT
|
||||||
#include <fast_float/fast_float.h>
|
#include <fast_float/fast_float.h>
|
||||||
#else
|
#else
|
||||||
#include <charconv>
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <cstdlib>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace ss {
|
namespace ss {
|
||||||
@ -24,8 +26,8 @@ namespace ss {
|
|||||||
#ifndef SSP_DISABLE_FAST_FLOAT
|
#ifndef SSP_DISABLE_FAST_FLOAT
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
std::enable_if_t<std::is_floating_point_v<T>, std::optional<T>> to_num(
|
[[nodiscard]] std::enable_if_t<std::is_floating_point_v<T>, std::optional<T>>
|
||||||
const char* const begin, const char* const end) {
|
to_num(const char* const begin, const char* const end) {
|
||||||
T ret;
|
T ret;
|
||||||
auto [ptr, ec] = fast_float::from_chars(begin, end, ret);
|
auto [ptr, ec] = fast_float::from_chars(begin, end, ret);
|
||||||
|
|
||||||
@ -38,7 +40,81 @@ std::enable_if_t<std::is_floating_point_v<T>, std::optional<T>> to_num(
|
|||||||
#else
|
#else
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
std::enable_if_t<std::is_floating_point_v<T>, std::optional<T>> to_num(
|
[[nodiscard]] std::enable_if_t<std::is_floating_point_v<T>, std::optional<T>>
|
||||||
|
to_num(const char* const begin, const char* const end) {
|
||||||
|
static_assert(!std::is_same_v<T, long double>,
|
||||||
|
"Conversion to long double is disabled");
|
||||||
|
|
||||||
|
constexpr static auto buff_max = 64;
|
||||||
|
std::array<char, buff_max> short_buff;
|
||||||
|
|
||||||
|
const size_t string_range = std::distance(begin, end);
|
||||||
|
std::string long_buff;
|
||||||
|
|
||||||
|
char* buff = nullptr;
|
||||||
|
if (string_range > buff_max) {
|
||||||
|
long_buff = std::string{begin, end};
|
||||||
|
buff = long_buff.data();
|
||||||
|
} else {
|
||||||
|
buff = short_buff.data();
|
||||||
|
buff[string_range] = '\0';
|
||||||
|
std::copy_n(begin, string_range, buff);
|
||||||
|
}
|
||||||
|
|
||||||
|
T ret;
|
||||||
|
char* parse_end = nullptr;
|
||||||
|
|
||||||
|
if constexpr (std::is_same_v<T, float>) {
|
||||||
|
ret = std::strtof(buff, &parse_end);
|
||||||
|
} else if constexpr (std::is_same_v<T, double>) {
|
||||||
|
ret = std::strtod(buff, &parse_end);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parse_end != buff + string_range) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
////////////////
|
||||||
|
// numeric_wrapper
|
||||||
|
////////////////
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct numeric_wrapper {
|
||||||
|
using type = T;
|
||||||
|
|
||||||
|
numeric_wrapper() = default;
|
||||||
|
numeric_wrapper(numeric_wrapper&&) noexcept = default;
|
||||||
|
numeric_wrapper(const numeric_wrapper&) = default;
|
||||||
|
|
||||||
|
numeric_wrapper& operator=(numeric_wrapper&&) noexcept = default;
|
||||||
|
numeric_wrapper& operator=(const numeric_wrapper&) = default;
|
||||||
|
|
||||||
|
~numeric_wrapper() = default;
|
||||||
|
|
||||||
|
numeric_wrapper(T other) : value{other} {
|
||||||
|
}
|
||||||
|
|
||||||
|
operator T() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
operator T() const {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
T value;
|
||||||
|
};
|
||||||
|
|
||||||
|
using int8 = numeric_wrapper<int8_t>;
|
||||||
|
using uint8 = numeric_wrapper<uint8_t>;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
[[nodiscard]] std::enable_if_t<std::is_integral_v<T>, std::optional<T>> to_num(
|
||||||
const char* const begin, const char* const end) {
|
const char* const begin, const char* const end) {
|
||||||
T ret;
|
T ret;
|
||||||
auto [ptr, ec] = std::from_chars(begin, end, ret);
|
auto [ptr, ec] = std::from_chars(begin, end, ret);
|
||||||
@ -49,196 +125,17 @@ std::enable_if_t<std::is_floating_point_v<T>, std::optional<T>> to_num(
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
inline std::optional<short> from_char(char c) {
|
|
||||||
if (c >= '0' && c <= '9') {
|
|
||||||
return c - '0';
|
|
||||||
}
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
#if defined(__clang__) && defined(__MINGW32__) && !defined(__MINGW64__)
|
|
||||||
#define MINGW32_CLANG
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// mingw32 clang does not support some of the builtin functions
|
|
||||||
#if (defined(__clang__) || defined(__GNUC__) || defined(__GUNG__)) && \
|
|
||||||
!defined(MINGW32_CLANG)
|
|
||||||
////////////////
|
|
||||||
// mul overflow detection
|
|
||||||
////////////////
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
bool mul_overflow(T& result, T operand) {
|
[[nodiscard]] std::enable_if_t<is_instance_of_v<numeric_wrapper, T>,
|
||||||
return __builtin_mul_overflow(result, operand, &result);
|
std::optional<T>>
|
||||||
}
|
to_num(const char* const begin, const char* const end) {
|
||||||
|
T ret;
|
||||||
|
auto [ptr, ec] = std::from_chars(begin, end, ret.value);
|
||||||
|
|
||||||
template <>
|
if (ec != std::errc() || ptr != end) {
|
||||||
inline bool mul_overflow(int& result, int operand) {
|
|
||||||
return __builtin_smul_overflow(result, operand, &result);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <>
|
|
||||||
inline bool mul_overflow(long& result, long operand) {
|
|
||||||
return __builtin_smull_overflow(result, operand, &result);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <>
|
|
||||||
inline bool mul_overflow(long long& result, long long operand) {
|
|
||||||
return __builtin_smulll_overflow(result, operand, &result);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <>
|
|
||||||
inline bool mul_overflow(unsigned int& result, unsigned int operand) {
|
|
||||||
return __builtin_umul_overflow(result, operand, &result);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <>
|
|
||||||
inline bool mul_overflow(unsigned long& result, unsigned long operand) {
|
|
||||||
return __builtin_umull_overflow(result, operand, &result);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <>
|
|
||||||
inline bool mul_overflow(unsigned long long& result,
|
|
||||||
unsigned long long operand) {
|
|
||||||
return __builtin_umulll_overflow(result, operand, &result);
|
|
||||||
}
|
|
||||||
|
|
||||||
////////////////
|
|
||||||
// addition overflow detection
|
|
||||||
////////////////
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
inline bool add_overflow(T& result, T operand) {
|
|
||||||
return __builtin_add_overflow(result, operand, &result);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <>
|
|
||||||
inline bool add_overflow(int& result, int operand) {
|
|
||||||
return __builtin_sadd_overflow(result, operand, &result);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <>
|
|
||||||
inline bool add_overflow(long& result, long operand) {
|
|
||||||
return __builtin_saddl_overflow(result, operand, &result);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <>
|
|
||||||
inline bool add_overflow(long long& result, long long operand) {
|
|
||||||
return __builtin_saddll_overflow(result, operand, &result);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <>
|
|
||||||
inline bool add_overflow(unsigned int& result, unsigned int operand) {
|
|
||||||
return __builtin_uadd_overflow(result, operand, &result);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <>
|
|
||||||
inline bool add_overflow(unsigned long& result, unsigned long operand) {
|
|
||||||
return __builtin_uaddl_overflow(result, operand, &result);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <>
|
|
||||||
inline bool add_overflow(unsigned long long& result,
|
|
||||||
unsigned long long operand) {
|
|
||||||
return __builtin_uaddll_overflow(result, operand, &result);
|
|
||||||
}
|
|
||||||
|
|
||||||
////////////////
|
|
||||||
// substraction overflow detection
|
|
||||||
////////////////
|
|
||||||
template <typename T>
|
|
||||||
inline bool sub_overflow(T& result, T operand) {
|
|
||||||
return __builtin_sub_overflow(result, operand, &result);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <>
|
|
||||||
inline bool sub_overflow(int& result, int operand) {
|
|
||||||
return __builtin_ssub_overflow(result, operand, &result);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <>
|
|
||||||
inline bool sub_overflow(long& result, long operand) {
|
|
||||||
return __builtin_ssubl_overflow(result, operand, &result);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <>
|
|
||||||
inline bool sub_overflow(long long& result, long long operand) {
|
|
||||||
return __builtin_ssubll_overflow(result, operand, &result);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <>
|
|
||||||
inline bool sub_overflow(unsigned int& result, unsigned int operand) {
|
|
||||||
return __builtin_usub_overflow(result, operand, &result);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <>
|
|
||||||
inline bool sub_overflow(unsigned long& result, unsigned long operand) {
|
|
||||||
return __builtin_usubl_overflow(result, operand, &result);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <>
|
|
||||||
inline bool sub_overflow(unsigned long long& result,
|
|
||||||
unsigned long long operand) {
|
|
||||||
return __builtin_usubll_overflow(result, operand, &result);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T, typename F>
|
|
||||||
bool shift_and_add_overflow(T& value, T digit, F add_last_digit_owerflow) {
|
|
||||||
if (mul_overflow<T>(value, 10) || add_last_digit_owerflow(value, digit)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
|
|
||||||
template <typename T, typename U>
|
|
||||||
bool shift_and_add_overflow(T& value, T digit, U is_negative) {
|
|
||||||
digit = (is_negative) ? -digit : digit;
|
|
||||||
T old_value = value;
|
|
||||||
value = 10 * value + digit;
|
|
||||||
|
|
||||||
T expected_old_value = (value - digit) / 10;
|
|
||||||
if (old_value != expected_old_value) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
std::enable_if_t<std::is_integral_v<T>, std::optional<T>> to_num(
|
|
||||||
const char* begin, const char* end) {
|
|
||||||
if (begin == end) {
|
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
bool is_negative = false;
|
return ret;
|
||||||
if constexpr (std::is_signed_v<T>) {
|
|
||||||
is_negative = *begin == '-';
|
|
||||||
if (is_negative) {
|
|
||||||
++begin;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#if (defined(__clang__) || defined(__GNUC__) || defined(__GUNG__)) && \
|
|
||||||
!defined(MINGW32_CLANG)
|
|
||||||
auto add_last_digit_owerflow =
|
|
||||||
(is_negative) ? sub_overflow<T> : add_overflow<T>;
|
|
||||||
#else
|
|
||||||
auto add_last_digit_owerflow = is_negative;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
T value = 0;
|
|
||||||
for (auto i = begin; i != end; ++i) {
|
|
||||||
if (auto digit = from_char(*i);
|
|
||||||
!digit || shift_and_add_overflow<T>(value, digit.value(),
|
|
||||||
add_last_digit_owerflow)) {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////
|
////////////////
|
||||||
@ -250,13 +147,15 @@ template <typename T>
|
|||||||
struct unsupported_type {
|
struct unsupported_type {
|
||||||
constexpr static bool value = false;
|
constexpr static bool value = false;
|
||||||
};
|
};
|
||||||
} /* namespace */
|
} /* namespace errors */
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
std::enable_if_t<!std::is_integral_v<T> && !std::is_floating_point_v<T> &&
|
[[nodiscard]] std::enable_if_t<!std::is_integral_v<T> &&
|
||||||
!is_instance_of_v<std::optional, T> &&
|
!std::is_floating_point_v<T> &&
|
||||||
!is_instance_of_v<std::variant, T>,
|
!is_instance_of_v<std::optional, T> &&
|
||||||
bool>
|
!is_instance_of_v<std::variant, T> &&
|
||||||
|
!is_instance_of_v<numeric_wrapper, T>,
|
||||||
|
bool>
|
||||||
extract(const char*, const char*, T&) {
|
extract(const char*, const char*, T&) {
|
||||||
static_assert(error::unsupported_type<T>::value,
|
static_assert(error::unsupported_type<T>::value,
|
||||||
"Conversion for given type is not defined, an "
|
"Conversion for given type is not defined, an "
|
||||||
@ -264,7 +163,10 @@ extract(const char*, const char*, T&) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
std::enable_if_t<std::is_integral_v<T> || std::is_floating_point_v<T>, bool>
|
[[nodiscard]] std::enable_if_t<std::is_integral_v<T> ||
|
||||||
|
std::is_floating_point_v<T> ||
|
||||||
|
is_instance_of_v<numeric_wrapper, T>,
|
||||||
|
bool>
|
||||||
extract(const char* begin, const char* end, T& value) {
|
extract(const char* begin, const char* end, T& value) {
|
||||||
auto optional_value = to_num<T>(begin, end);
|
auto optional_value = to_num<T>(begin, end);
|
||||||
if (!optional_value) {
|
if (!optional_value) {
|
||||||
@ -275,8 +177,8 @@ extract(const char* begin, const char* end, T& value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
std::enable_if_t<is_instance_of_v<std::optional, T>, bool> extract(
|
[[nodiscard]] std::enable_if_t<is_instance_of_v<std::optional, T>, bool>
|
||||||
const char* begin, const char* end, T& value) {
|
extract(const char* begin, const char* end, T& value) {
|
||||||
typename T::value_type raw_value;
|
typename T::value_type raw_value;
|
||||||
if (extract(begin, end, raw_value)) {
|
if (extract(begin, end, raw_value)) {
|
||||||
value = raw_value;
|
value = raw_value;
|
||||||
@ -287,7 +189,8 @@ std::enable_if_t<is_instance_of_v<std::optional, T>, bool> extract(
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename T, size_t I>
|
template <typename T, size_t I>
|
||||||
bool extract_variant(const char* begin, const char* end, T& value) {
|
[[nodiscard]] bool extract_variant(const char* begin, const char* end,
|
||||||
|
T& value) {
|
||||||
using IthType = std::variant_alternative_t<I, std::decay_t<T>>;
|
using IthType = std::variant_alternative_t<I, std::decay_t<T>>;
|
||||||
IthType ithValue;
|
IthType ithValue;
|
||||||
if (extract<IthType>(begin, end, ithValue)) {
|
if (extract<IthType>(begin, end, ithValue)) {
|
||||||
@ -300,7 +203,7 @@ bool extract_variant(const char* begin, const char* end, T& value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
std::enable_if_t<is_instance_of_v<std::variant, T>, bool> extract(
|
[[nodiscard]] std::enable_if_t<is_instance_of_v<std::variant, T>, bool> extract(
|
||||||
const char* begin, const char* end, T& value) {
|
const char* begin, const char* end, T& value) {
|
||||||
return extract_variant<T, 0>(begin, end, value);
|
return extract_variant<T, 0>(begin, end, value);
|
||||||
}
|
}
|
||||||
@ -310,7 +213,8 @@ std::enable_if_t<is_instance_of_v<std::variant, T>, bool> extract(
|
|||||||
////////////////
|
////////////////
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
inline bool extract(const char* begin, const char* end, bool& value) {
|
[[nodiscard]] inline bool extract(const char* begin, const char* end,
|
||||||
|
bool& value) {
|
||||||
if (end == begin + 1) {
|
if (end == begin + 1) {
|
||||||
if (*begin == '1') {
|
if (*begin == '1') {
|
||||||
value = true;
|
value = true;
|
||||||
@ -320,10 +224,13 @@ inline bool extract(const char* begin, const char* end, bool& value) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
size_t size = end - begin;
|
constexpr static auto true_size = 4;
|
||||||
if (size == 4 && strncmp(begin, "true", size) == 0) {
|
constexpr static auto false_size = 5;
|
||||||
|
const size_t size = end - begin;
|
||||||
|
if (size == true_size && std::strncmp(begin, "true", size) == 0) {
|
||||||
value = true;
|
value = true;
|
||||||
} else if (size == 5 && strncmp(begin, "false", size) == 0) {
|
} else if (size == false_size &&
|
||||||
|
std::strncmp(begin, "false", size) == 0) {
|
||||||
value = false;
|
value = false;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
@ -334,22 +241,24 @@ inline bool extract(const char* begin, const char* end, bool& value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
inline bool extract(const char* begin, const char* end, char& value) {
|
[[nodiscard]] inline bool extract(const char* begin, const char* end,
|
||||||
|
char& value) {
|
||||||
value = *begin;
|
value = *begin;
|
||||||
return (end == begin + 1);
|
return (end == begin + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
inline bool extract(const char* begin, const char* end, std::string& value) {
|
[[nodiscard]] inline bool extract(const char* begin, const char* end,
|
||||||
|
std::string& value) {
|
||||||
value = std::string{begin, end};
|
value = std::string{begin, end};
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
inline bool extract(const char* begin, const char* end,
|
[[nodiscard]] inline bool extract(const char* begin, const char* end,
|
||||||
std::string_view& value) {
|
std::string_view& value) {
|
||||||
value = std::string_view{begin, static_cast<size_t>(end - begin)};
|
value = std::string_view{begin, static_cast<size_t>(end - begin)};
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
} /* ss */
|
} /* namespace ss */
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <tuple>
|
|
||||||
|
|
||||||
namespace ss {
|
namespace ss {
|
||||||
|
|
||||||
@ -77,4 +76,4 @@ struct member_wrapper<R T::*> {
|
|||||||
template <typename T> \
|
template <typename T> \
|
||||||
constexpr bool has_m_##method##_t = has_m_##method<T>::value;
|
constexpr bool has_m_##method##_t = has_m_##method<T>::value;
|
||||||
|
|
||||||
} /* trait */
|
} /* namespace ss */
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -10,7 +10,7 @@ template <typename T, auto... Values>
|
|||||||
struct ax {
|
struct ax {
|
||||||
private:
|
private:
|
||||||
template <auto X, auto... Xs>
|
template <auto X, auto... Xs>
|
||||||
bool ss_valid_impl(const T& x) const {
|
[[nodiscard]] bool ss_valid_impl(const T& x) const {
|
||||||
if constexpr (sizeof...(Xs) != 0) {
|
if constexpr (sizeof...(Xs) != 0) {
|
||||||
return x != X && ss_valid_impl<Xs...>(x);
|
return x != X && ss_valid_impl<Xs...>(x);
|
||||||
}
|
}
|
||||||
@ -18,11 +18,11 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
bool ss_valid(const T& value) const {
|
[[nodiscard]] bool ss_valid(const T& value) const {
|
||||||
return ss_valid_impl<Values...>(value);
|
return ss_valid_impl<Values...>(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* error() const {
|
[[nodiscard]] const char* error() const {
|
||||||
return "value excluded";
|
return "value excluded";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -35,7 +35,7 @@ template <typename T, auto... Values>
|
|||||||
struct nx {
|
struct nx {
|
||||||
private:
|
private:
|
||||||
template <auto X, auto... Xs>
|
template <auto X, auto... Xs>
|
||||||
bool ss_valid_impl(const T& x) const {
|
[[nodiscard]] bool ss_valid_impl(const T& x) const {
|
||||||
if constexpr (sizeof...(Xs) != 0) {
|
if constexpr (sizeof...(Xs) != 0) {
|
||||||
return x == X || ss_valid_impl<Xs...>(x);
|
return x == X || ss_valid_impl<Xs...>(x);
|
||||||
}
|
}
|
||||||
@ -43,11 +43,11 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
bool ss_valid(const T& value) const {
|
[[nodiscard]] bool ss_valid(const T& value) const {
|
||||||
return ss_valid_impl<Values...>(value);
|
return ss_valid_impl<Values...>(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* error() const {
|
[[nodiscard]] const char* error() const {
|
||||||
return "value excluded";
|
return "value excluded";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -61,28 +61,28 @@ public:
|
|||||||
|
|
||||||
template <typename T, auto N>
|
template <typename T, auto N>
|
||||||
struct gt {
|
struct gt {
|
||||||
bool ss_valid(const T& value) const {
|
[[nodiscard]] bool ss_valid(const T& value) const {
|
||||||
return value > N;
|
return value > N;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T, auto N>
|
template <typename T, auto N>
|
||||||
struct gte {
|
struct gte {
|
||||||
bool ss_valid(const T& value) const {
|
[[nodiscard]] bool ss_valid(const T& value) const {
|
||||||
return value >= N;
|
return value >= N;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T, auto N>
|
template <typename T, auto N>
|
||||||
struct lt {
|
struct lt {
|
||||||
bool ss_valid(const T& value) const {
|
[[nodiscard]] bool ss_valid(const T& value) const {
|
||||||
return value < N;
|
return value < N;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T, auto N>
|
template <typename T, auto N>
|
||||||
struct lte {
|
struct lte {
|
||||||
bool ss_valid(const T& value) const {
|
[[nodiscard]] bool ss_valid(const T& value) const {
|
||||||
return value <= N;
|
return value <= N;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -93,7 +93,7 @@ struct lte {
|
|||||||
|
|
||||||
template <typename T, auto Min, auto Max>
|
template <typename T, auto Min, auto Max>
|
||||||
struct ir {
|
struct ir {
|
||||||
bool ss_valid(const T& value) const {
|
[[nodiscard]] bool ss_valid(const T& value) const {
|
||||||
return value >= Min && value <= Max;
|
return value >= Min && value <= Max;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -104,7 +104,7 @@ struct ir {
|
|||||||
|
|
||||||
template <typename T, auto Min, auto Max>
|
template <typename T, auto Min, auto Max>
|
||||||
struct oor {
|
struct oor {
|
||||||
bool ss_valid(const T& value) const {
|
[[nodiscard]] bool ss_valid(const T& value) const {
|
||||||
return value < Min || value > Max;
|
return value < Min || value > Max;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -115,13 +115,13 @@ struct oor {
|
|||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
struct ne {
|
struct ne {
|
||||||
bool ss_valid(const T& value) const {
|
[[nodiscard]] bool ss_valid(const T& value) const {
|
||||||
return !value.empty();
|
return !value.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* error() const {
|
[[nodiscard]] const char* error() const {
|
||||||
return "empty field";
|
return "empty field";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* ss */
|
} /* namespace ss */
|
||||||
|
@ -109,7 +109,7 @@ struct get_matcher<Matcher, T, Ts...> {
|
|||||||
struct is_matcher : is_instance_of_matcher<U, Matcher> {};
|
struct is_matcher : is_instance_of_matcher<U, Matcher> {};
|
||||||
|
|
||||||
static_assert(count_v<is_matcher, T, Ts...> <= 1,
|
static_assert(count_v<is_matcher, T, Ts...> <= 1,
|
||||||
"the same matcher cannot"
|
"the same matcher cannot "
|
||||||
"be defined multiple times");
|
"be defined multiple times");
|
||||||
using type = std::conditional_t<is_matcher<T>::value, T,
|
using type = std::conditional_t<is_matcher<T>::value, T,
|
||||||
typename get_matcher<Matcher, Ts...>::type>;
|
typename get_matcher<Matcher, Ts...>::type>;
|
||||||
@ -165,34 +165,40 @@ using get_multiline_t = typename get_multiline<Ts...>::type;
|
|||||||
// string_error
|
// string_error
|
||||||
////////////////
|
////////////////
|
||||||
|
|
||||||
class string_error;
|
class string_error {};
|
||||||
|
|
||||||
////////////////
|
////////////////
|
||||||
// ignore_header
|
// ignore_header
|
||||||
////////////////
|
////////////////
|
||||||
|
|
||||||
class ignore_header;
|
class ignore_header {};
|
||||||
|
|
||||||
////////////////
|
////////////////
|
||||||
// ignore_empty
|
// ignore_empty
|
||||||
////////////////
|
////////////////
|
||||||
|
|
||||||
class ignore_empty;
|
class ignore_empty {};
|
||||||
|
|
||||||
|
////////////////
|
||||||
|
// throw_on_error
|
||||||
|
////////////////
|
||||||
|
|
||||||
|
class throw_on_error {};
|
||||||
|
|
||||||
////////////////
|
////////////////
|
||||||
// setup implementation
|
// setup implementation
|
||||||
////////////////
|
////////////////
|
||||||
|
|
||||||
template <typename... Ts>
|
template <typename... Options>
|
||||||
struct setup {
|
struct setup {
|
||||||
private:
|
private:
|
||||||
template <typename T>
|
template <typename Option>
|
||||||
struct is_matcher
|
struct is_matcher
|
||||||
: std::disjunction<is_instance_of_matcher_t<T, quote>,
|
: std::disjunction<is_instance_of_matcher_t<Option, quote>,
|
||||||
is_instance_of_matcher_t<T, escape>,
|
is_instance_of_matcher_t<Option, escape>,
|
||||||
is_instance_of_matcher_t<T, trim>,
|
is_instance_of_matcher_t<Option, trim>,
|
||||||
is_instance_of_matcher_t<T, trim_left>,
|
is_instance_of_matcher_t<Option, trim_left>,
|
||||||
is_instance_of_matcher_t<T, trim_right>> {};
|
is_instance_of_matcher_t<Option, trim_right>> {};
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
struct is_string_error : std::is_same<T, string_error> {};
|
struct is_string_error : std::is_same<T, string_error> {};
|
||||||
@ -203,39 +209,48 @@ private:
|
|||||||
template <typename T>
|
template <typename T>
|
||||||
struct is_ignore_empty : std::is_same<T, ignore_empty> {};
|
struct is_ignore_empty : std::is_same<T, ignore_empty> {};
|
||||||
|
|
||||||
constexpr static auto count_matcher = count_v<is_matcher, Ts...>;
|
template <typename T>
|
||||||
|
struct is_throw_on_error : std::is_same<T, throw_on_error> {};
|
||||||
|
|
||||||
|
constexpr static auto count_matcher = count_v<is_matcher, Options...>;
|
||||||
|
|
||||||
constexpr static auto count_multiline =
|
constexpr static auto count_multiline =
|
||||||
count_v<is_instance_of_multiline, Ts...>;
|
count_v<is_instance_of_multiline, Options...>;
|
||||||
|
|
||||||
constexpr static auto count_string_error = count_v<is_string_error, Ts...>;
|
constexpr static auto count_string_error =
|
||||||
|
count_v<is_string_error, Options...>;
|
||||||
|
|
||||||
constexpr static auto count_ignore_header =
|
constexpr static auto count_ignore_header =
|
||||||
count_v<is_ignore_header, Ts...>;
|
count_v<is_ignore_header, Options...>;
|
||||||
|
|
||||||
constexpr static auto count_ignore_empty = count_v<is_ignore_empty, Ts...>;
|
constexpr static auto count_throw_on_error =
|
||||||
|
count_v<is_throw_on_error, Options...>;
|
||||||
|
|
||||||
|
constexpr static auto count_ignore_empty =
|
||||||
|
count_v<is_ignore_empty, Options...>;
|
||||||
|
|
||||||
constexpr static auto number_of_valid_setup_types =
|
constexpr static auto number_of_valid_setup_types =
|
||||||
count_matcher + count_multiline + count_string_error +
|
count_matcher + count_multiline + count_string_error +
|
||||||
count_ignore_header + count_ignore_empty;
|
count_ignore_header + count_ignore_empty + count_throw_on_error;
|
||||||
|
|
||||||
using trim_left_only = get_matcher_t<trim_left, Ts...>;
|
using trim_left_only = get_matcher_t<trim_left, Options...>;
|
||||||
using trim_right_only = get_matcher_t<trim_right, Ts...>;
|
using trim_right_only = get_matcher_t<trim_right, Options...>;
|
||||||
using trim_all = get_matcher_t<trim, Ts...>;
|
using trim_all = get_matcher_t<trim, Options...>;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using quote = get_matcher_t<quote, Ts...>;
|
using quote = get_matcher_t<quote, Options...>;
|
||||||
using escape = get_matcher_t<escape, Ts...>;
|
using escape = get_matcher_t<escape, Options...>;
|
||||||
|
|
||||||
using trim_left =
|
using trim_left =
|
||||||
std::conditional_t<trim_all::enabled, trim_all, trim_left_only>;
|
std::conditional_t<trim_all::enabled, trim_all, trim_left_only>;
|
||||||
using trim_right =
|
using trim_right =
|
||||||
std::conditional_t<trim_all::enabled, trim_all, trim_right_only>;
|
std::conditional_t<trim_all::enabled, trim_all, trim_right_only>;
|
||||||
|
|
||||||
using multiline = get_multiline_t<Ts...>;
|
using multiline = get_multiline_t<Options...>;
|
||||||
constexpr static bool string_error = (count_string_error == 1);
|
constexpr static bool string_error = (count_string_error == 1);
|
||||||
constexpr static bool ignore_header = (count_ignore_header == 1);
|
constexpr static bool ignore_header = (count_ignore_header == 1);
|
||||||
constexpr static bool ignore_empty = (count_ignore_empty == 1);
|
constexpr static bool ignore_empty = (count_ignore_empty == 1);
|
||||||
|
constexpr static bool throw_on_error = (count_throw_on_error == 1);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
#define ASSERT_MSG "cannot have the same match character in multiple matchers"
|
#define ASSERT_MSG "cannot have the same match character in multiple matchers"
|
||||||
@ -254,21 +269,31 @@ private:
|
|||||||
static_assert(
|
static_assert(
|
||||||
!multiline::enabled ||
|
!multiline::enabled ||
|
||||||
(multiline::enabled && (quote::enabled || escape::enabled)),
|
(multiline::enabled && (quote::enabled || escape::enabled)),
|
||||||
"to enable multiline either quote or escape need to be enabled");
|
"to enable multiline either quote or escape needs to be enabled");
|
||||||
|
|
||||||
static_assert(!(trim_all::enabled && trim_left_only::enabled) &&
|
static_assert(!(trim_all::enabled && trim_left_only::enabled) &&
|
||||||
!(trim_all::enabled && trim_right_only::enabled),
|
!(trim_all::enabled && trim_right_only::enabled),
|
||||||
"ambiguous trim setup");
|
"ambiguous trim setup");
|
||||||
|
|
||||||
static_assert(count_multiline <= 1, "mutliline defined multiple times");
|
static_assert(count_multiline <= 1, "mutliline defined multiple times");
|
||||||
|
|
||||||
static_assert(count_string_error <= 1,
|
static_assert(count_string_error <= 1,
|
||||||
"string_error defined multiple times");
|
"string_error defined multiple times");
|
||||||
|
|
||||||
static_assert(number_of_valid_setup_types == sizeof...(Ts),
|
static_assert(count_throw_on_error <= 1,
|
||||||
|
"throw_on_error defined multiple times");
|
||||||
|
|
||||||
|
static_assert(count_throw_on_error + count_string_error <= 1,
|
||||||
|
"cannot define both throw_on_error and string_error");
|
||||||
|
|
||||||
|
static_assert(number_of_valid_setup_types == sizeof...(Options),
|
||||||
"one or multiple invalid setup parameters defined");
|
"one or multiple invalid setup parameters defined");
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename... Ts>
|
template <typename... Options>
|
||||||
struct setup<setup<Ts...>> : setup<Ts...> {};
|
struct setup<setup<Options...>> : setup<Options...> {};
|
||||||
|
|
||||||
} /* ss */
|
template <typename... Options>
|
||||||
|
struct setup<std::tuple<Options...>> : setup<Options...> {};
|
||||||
|
|
||||||
|
} /* namespace ss */
|
||||||
|
@ -1,26 +1,26 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "common.hpp"
|
#include "common.hpp"
|
||||||
|
#include "exception.hpp"
|
||||||
#include "setup.hpp"
|
#include "setup.hpp"
|
||||||
#include "type_traits.hpp"
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace ss {
|
namespace ss {
|
||||||
|
|
||||||
template <typename... Ts>
|
template <typename... Options>
|
||||||
class splitter {
|
class splitter {
|
||||||
private:
|
private:
|
||||||
using quote = typename setup<Ts...>::quote;
|
using quote = typename setup<Options...>::quote;
|
||||||
using trim_left = typename setup<Ts...>::trim_left;
|
using trim_left = typename setup<Options...>::trim_left;
|
||||||
using trim_right = typename setup<Ts...>::trim_right;
|
using trim_right = typename setup<Options...>::trim_right;
|
||||||
using escape = typename setup<Ts...>::escape;
|
using escape = typename setup<Options...>::escape;
|
||||||
using multiline = typename setup<Ts...>::multiline;
|
using multiline = typename setup<Options...>::multiline;
|
||||||
|
|
||||||
constexpr static auto string_error = setup<Ts...>::string_error;
|
constexpr static auto string_error = setup<Options...>::string_error;
|
||||||
|
constexpr static auto throw_on_error = setup<Options...>::throw_on_error;
|
||||||
constexpr static auto is_const_line = !quote::enabled && !escape::enabled;
|
constexpr static auto is_const_line = !quote::enabled && !escape::enabled;
|
||||||
|
|
||||||
using error_type = std::conditional_t<string_error, std::string, bool>;
|
using error_type = std::conditional_t<string_error, std::string, bool>;
|
||||||
@ -28,20 +28,22 @@ private:
|
|||||||
public:
|
public:
|
||||||
using line_ptr_type = std::conditional_t<is_const_line, const char*, char*>;
|
using line_ptr_type = std::conditional_t<is_const_line, const char*, char*>;
|
||||||
|
|
||||||
bool valid() const {
|
[[nodiscard]] bool valid() const {
|
||||||
if constexpr (string_error) {
|
if constexpr (string_error) {
|
||||||
return error_.empty();
|
return error_.empty();
|
||||||
|
} else if constexpr (throw_on_error) {
|
||||||
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return !error_;
|
return !error_;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string& error_msg() const {
|
[[nodiscard]] const std::string& error_msg() const {
|
||||||
assert_string_error_defined<string_error>();
|
assert_string_error_defined<string_error>();
|
||||||
return error_;
|
return error_;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool unterminated_quote() const {
|
[[nodiscard]] bool unterminated_quote() const {
|
||||||
return unterminated_quote_;
|
return unterminated_quote_;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,13 +55,21 @@ public:
|
|||||||
return split_impl_select_delim(delimiter);
|
return split_impl_select_delim(delimiter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const split_data& get_split_data() const {
|
||||||
|
return split_data_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear_split_data() {
|
||||||
|
split_data_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
////////////////
|
////////////////
|
||||||
// resplit
|
// resplit
|
||||||
////////////////
|
////////////////
|
||||||
|
|
||||||
// number of characters the end of line is shifted backwards
|
// number of characters the end of line is shifted backwards
|
||||||
size_t size_shifted() const {
|
[[nodiscard]] size_t size_shifted() const {
|
||||||
return escaped_;
|
return escaped_;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,16 +87,16 @@ private:
|
|||||||
// resplitting, continue from last slice
|
// resplitting, continue from last slice
|
||||||
if (!quote::enabled || !multiline::enabled || split_data_.empty() ||
|
if (!quote::enabled || !multiline::enabled || split_data_.empty() ||
|
||||||
!unterminated_quote()) {
|
!unterminated_quote()) {
|
||||||
set_error_invalid_resplit();
|
handle_error_invalid_resplit();
|
||||||
return split_data_;
|
return split_data_;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto [old_line, old_begin] = *std::prev(split_data_.end());
|
const auto [old_line, old_begin] = *std::prev(split_data_.end());
|
||||||
size_t begin = old_begin - old_line - 1;
|
const size_t begin = old_begin - old_line - 1;
|
||||||
|
|
||||||
// safety measure
|
// safety measure
|
||||||
if (new_size != -1 && static_cast<size_t>(new_size) < begin) {
|
if (new_size != -1 && static_cast<size_t>(new_size) < begin) {
|
||||||
set_error_invalid_resplit();
|
handle_error_invalid_resplit();
|
||||||
return split_data_;
|
return split_data_;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,55 +122,75 @@ private:
|
|||||||
void clear_error() {
|
void clear_error() {
|
||||||
if constexpr (string_error) {
|
if constexpr (string_error) {
|
||||||
error_.clear();
|
error_.clear();
|
||||||
} else {
|
} else if constexpr (!throw_on_error) {
|
||||||
error_ = false;
|
error_ = false;
|
||||||
}
|
}
|
||||||
unterminated_quote_ = false;
|
unterminated_quote_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_error_empty_delimiter() {
|
void handle_error_empty_delimiter() {
|
||||||
|
constexpr static auto error_msg = "empty delimiter";
|
||||||
|
|
||||||
if constexpr (string_error) {
|
if constexpr (string_error) {
|
||||||
error_.clear();
|
error_.clear();
|
||||||
error_.append("empt delimiter");
|
error_.append(error_msg);
|
||||||
|
} else if constexpr (throw_on_error) {
|
||||||
|
throw ss::exception{error_msg};
|
||||||
} else {
|
} else {
|
||||||
error_ = true;
|
error_ = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_error_mismatched_quote(size_t n) {
|
void handle_error_mismatched_quote(size_t n) {
|
||||||
|
constexpr static auto error_msg = "mismatched quote at position: ";
|
||||||
|
|
||||||
if constexpr (string_error) {
|
if constexpr (string_error) {
|
||||||
error_.clear();
|
error_.clear();
|
||||||
error_.append("mismatched quote at position: " + std::to_string(n));
|
error_.append(error_msg + std::to_string(n));
|
||||||
|
} else if constexpr (throw_on_error) {
|
||||||
|
throw ss::exception{error_msg + std::to_string(n)};
|
||||||
} else {
|
} else {
|
||||||
error_ = true;
|
error_ = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_error_unterminated_escape() {
|
void handle_error_unterminated_escape() {
|
||||||
|
constexpr static auto error_msg =
|
||||||
|
"unterminated escape at the end of the line";
|
||||||
|
|
||||||
if constexpr (string_error) {
|
if constexpr (string_error) {
|
||||||
error_.clear();
|
error_.clear();
|
||||||
error_.append("unterminated escape at the end of the line");
|
error_.append(error_msg);
|
||||||
|
} else if constexpr (throw_on_error) {
|
||||||
|
throw ss::exception{error_msg};
|
||||||
} else {
|
} else {
|
||||||
error_ = true;
|
error_ = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_error_unterminated_quote() {
|
void handle_error_unterminated_quote() {
|
||||||
unterminated_quote_ = true;
|
constexpr static auto error_msg = "unterminated quote";
|
||||||
|
|
||||||
if constexpr (string_error) {
|
if constexpr (string_error) {
|
||||||
error_.clear();
|
error_.clear();
|
||||||
error_.append("unterminated quote");
|
error_.append(error_msg);
|
||||||
|
} else if constexpr (throw_on_error) {
|
||||||
|
throw ss::exception{error_msg};
|
||||||
} else {
|
} else {
|
||||||
error_ = true;
|
error_ = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_error_invalid_resplit() {
|
void handle_error_invalid_resplit() {
|
||||||
unterminated_quote_ = false;
|
constexpr static auto error_msg =
|
||||||
|
"invalid resplit, new line must be longer"
|
||||||
|
"than the end of the last slice";
|
||||||
|
|
||||||
if constexpr (string_error) {
|
if constexpr (string_error) {
|
||||||
error_.clear();
|
error_.clear();
|
||||||
error_.append("invalid resplit, new line must be longer"
|
error_.append(error_msg);
|
||||||
"than the end of the last slice");
|
} else if constexpr (throw_on_error) {
|
||||||
|
throw ss::exception{error_msg};
|
||||||
} else {
|
} else {
|
||||||
error_ = true;
|
error_ = true;
|
||||||
}
|
}
|
||||||
@ -170,19 +200,19 @@ private:
|
|||||||
// matching
|
// matching
|
||||||
////////////////
|
////////////////
|
||||||
|
|
||||||
bool match(const char* const curr, char delim) {
|
[[nodiscard]] bool match(const char* const curr, char delim) {
|
||||||
return *curr == delim;
|
return *curr == delim;
|
||||||
};
|
};
|
||||||
|
|
||||||
bool match(const char* const curr, const std::string& delim) {
|
[[nodiscard]] bool match(const char* const curr, const std::string& delim) {
|
||||||
return strncmp(curr, delim.c_str(), delim.size()) == 0;
|
return std::strncmp(curr, delim.c_str(), delim.size()) == 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
size_t delimiter_size(char) {
|
[[nodiscard]] size_t delimiter_size(char) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t delimiter_size(const std::string& delim) {
|
[[nodiscard]] size_t delimiter_size(const std::string& delim) {
|
||||||
return delim.size();
|
return delim.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,8 +233,8 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename Delim>
|
template <typename Delim>
|
||||||
std::tuple<size_t, bool> match_delimiter(line_ptr_type begin,
|
[[nodiscard]] std::tuple<size_t, bool> match_delimiter(line_ptr_type begin,
|
||||||
const Delim& delim) {
|
const Delim& delim) {
|
||||||
line_ptr_type end = begin;
|
line_ptr_type end = begin;
|
||||||
|
|
||||||
trim_right_if_enabled(end);
|
trim_right_if_enabled(end);
|
||||||
@ -235,7 +265,9 @@ private:
|
|||||||
if constexpr (escape::enabled) {
|
if constexpr (escape::enabled) {
|
||||||
if (escape::match(*curr)) {
|
if (escape::match(*curr)) {
|
||||||
if (curr[1] == '\0') {
|
if (curr[1] == '\0') {
|
||||||
set_error_unterminated_escape();
|
if constexpr (!multiline::enabled) {
|
||||||
|
handle_error_unterminated_escape();
|
||||||
|
}
|
||||||
done_ = true;
|
done_ = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -282,7 +314,7 @@ private:
|
|||||||
clear_error();
|
clear_error();
|
||||||
switch (delimiter.size()) {
|
switch (delimiter.size()) {
|
||||||
case 0:
|
case 0:
|
||||||
set_error_empty_delimiter();
|
handle_error_empty_delimiter();
|
||||||
return split_data_;
|
return split_data_;
|
||||||
case 1:
|
case 1:
|
||||||
return split_impl(delimiter[0]);
|
return split_impl(delimiter[0]);
|
||||||
@ -296,8 +328,9 @@ private:
|
|||||||
|
|
||||||
trim_left_if_enabled(begin_);
|
trim_left_if_enabled(begin_);
|
||||||
|
|
||||||
for (done_ = false; !done_; read(delim))
|
for (done_ = false; !done_;) {
|
||||||
;
|
read(delim);
|
||||||
|
}
|
||||||
|
|
||||||
return split_data_;
|
return split_data_;
|
||||||
}
|
}
|
||||||
@ -362,7 +395,9 @@ private:
|
|||||||
if (end_[1] == '\0') {
|
if (end_[1] == '\0') {
|
||||||
// eol, unterminated escape
|
// eol, unterminated escape
|
||||||
// eg: ... "hel\\0
|
// eg: ... "hel\\0
|
||||||
set_error_unterminated_escape();
|
if constexpr (!multiline::enabled) {
|
||||||
|
handle_error_unterminated_escape();
|
||||||
|
}
|
||||||
done_ = true;
|
done_ = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -379,7 +414,10 @@ private:
|
|||||||
// eg: ..."hell\0 -> quote not terminated
|
// eg: ..."hell\0 -> quote not terminated
|
||||||
if (*end_ == '\0') {
|
if (*end_ == '\0') {
|
||||||
shift_and_set_current();
|
shift_and_set_current();
|
||||||
set_error_unterminated_quote();
|
unterminated_quote_ = true;
|
||||||
|
if constexpr (!multiline::enabled) {
|
||||||
|
handle_error_unterminated_quote();
|
||||||
|
}
|
||||||
split_data_.emplace_back(line_, begin_);
|
split_data_.emplace_back(line_, begin_);
|
||||||
done_ = true;
|
done_ = true;
|
||||||
break;
|
break;
|
||||||
@ -418,7 +456,7 @@ private:
|
|||||||
} else {
|
} else {
|
||||||
// mismatched quote
|
// mismatched quote
|
||||||
// eg: ...,"hel"lo,... -> error
|
// eg: ...,"hel"lo,... -> error
|
||||||
set_error_mismatched_quote(end_ - line_);
|
handle_error_mismatched_quote(end_ - line_);
|
||||||
split_data_.emplace_back(line_, begin_);
|
split_data_.emplace_back(line_, begin_);
|
||||||
}
|
}
|
||||||
done_ = true;
|
done_ = true;
|
||||||
@ -431,7 +469,6 @@ private:
|
|||||||
// members
|
// members
|
||||||
////////////////
|
////////////////
|
||||||
|
|
||||||
public:
|
|
||||||
error_type error_{};
|
error_type error_{};
|
||||||
bool unterminated_quote_{false};
|
bool unterminated_quote_{false};
|
||||||
bool done_{true};
|
bool done_{true};
|
||||||
@ -448,4 +485,4 @@ public:
|
|||||||
friend class converter;
|
friend class converter;
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* ss */
|
} /* namespace ss */
|
||||||
|
@ -34,7 +34,11 @@ struct left_of_impl;
|
|||||||
|
|
||||||
template <size_t N, typename T, typename... Ts>
|
template <size_t N, typename T, typename... Ts>
|
||||||
struct left_of_impl {
|
struct left_of_impl {
|
||||||
static_assert(N < 128, "recursion limit reached");
|
private:
|
||||||
|
constexpr static auto recursion_limit = 128;
|
||||||
|
|
||||||
|
public:
|
||||||
|
static_assert(N < recursion_limit, "recursion limit reached");
|
||||||
static_assert(N != 0, "cannot take the whole tuple");
|
static_assert(N != 0, "cannot take the whole tuple");
|
||||||
using type = tup_cat_t<T, typename left_of_impl<N - 1, Ts...>::type>;
|
using type = tup_cat_t<T, typename left_of_impl<N - 1, Ts...>::type>;
|
||||||
};
|
};
|
||||||
@ -362,12 +366,12 @@ constexpr bool is_instance_of_v = is_instance_of<Template, Ts...>::value;
|
|||||||
////////////////
|
////////////////
|
||||||
|
|
||||||
template <class T, std::size_t... Is, class U>
|
template <class T, std::size_t... Is, class U>
|
||||||
T to_object_impl(std::index_sequence<Is...>, U&& data) {
|
[[nodiscard]] T to_object_impl(std::index_sequence<Is...>, U&& data) {
|
||||||
return {std::get<Is>(std::forward<U>(data))...};
|
return {std::get<Is>(std::forward<U>(data))...};
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class T, class U>
|
template <class T, class U>
|
||||||
T to_object(U&& data) {
|
[[nodiscard]] T to_object(U&& data) {
|
||||||
using NoRefU = std::decay_t<U>;
|
using NoRefU = std::decay_t<U>;
|
||||||
if constexpr (is_instance_of_v<std::tuple, NoRefU>) {
|
if constexpr (is_instance_of_v<std::tuple, NoRefU>) {
|
||||||
return to_object_impl<
|
return to_object_impl<
|
||||||
@ -378,4 +382,4 @@ T to_object(U&& data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} /* trait */
|
} /* namespace ss */
|
||||||
|
@ -6,7 +6,7 @@ project(
|
|||||||
'cpp_std=c++17',
|
'cpp_std=c++17',
|
||||||
'buildtype=debugoptimized',
|
'buildtype=debugoptimized',
|
||||||
'wrap_mode=forcefallback'],
|
'wrap_mode=forcefallback'],
|
||||||
version: '1.4.0',
|
version: '1.8.0',
|
||||||
meson_version:'>=0.54.0')
|
meson_version:'>=0.54.0')
|
||||||
|
|
||||||
fast_float_dep = dependency('fast_float')
|
fast_float_dep = dependency('fast_float')
|
||||||
|
6
script/ci_install_lcov.sh
Executable file
6
script/ci_install_lcov.sh
Executable file
@ -0,0 +1,6 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
echo yes | cpan DateTime Capture::Tiny
|
||||||
|
|
||||||
|
wget -qO- https://github.com/linux-test-project/lcov/releases/download/v2.0/lcov-2.0.tar.gz | tar xvz
|
||||||
|
(cd lcov-2.0 && make install)
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
headers_dir = 'include/ss/'
|
headers_dir = 'include/ss/'
|
||||||
headers = ['type_traits.hpp',
|
headers = ['type_traits.hpp',
|
||||||
|
'exception.hpp',
|
||||||
'function_traits.hpp',
|
'function_traits.hpp',
|
||||||
'restrictions.hpp',
|
'restrictions.hpp',
|
||||||
'common.hpp',
|
'common.hpp',
|
||||||
@ -13,14 +14,21 @@ headers = ['type_traits.hpp',
|
|||||||
|
|
||||||
combined_file = []
|
combined_file = []
|
||||||
includes = []
|
includes = []
|
||||||
|
in_pp_block = False
|
||||||
|
|
||||||
for header in headers:
|
for header in headers:
|
||||||
with open(headers_dir + header) as f:
|
with open(headers_dir + header) as f:
|
||||||
for line in f.read().splitlines():
|
for line in f.read().splitlines():
|
||||||
|
if '#if ' in line:
|
||||||
|
in_pp_block = True
|
||||||
|
|
||||||
|
if '#endif' in line:
|
||||||
|
in_pp_block = False
|
||||||
|
|
||||||
if '#include "' in line or '#include <fast_float' in line:
|
if '#include "' in line or '#include <fast_float' in line:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if '#include <' in line:
|
if '#include <' in line and not in_pp_block:
|
||||||
includes.append(line)
|
includes.append(line)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -29,6 +37,7 @@ for header in headers:
|
|||||||
|
|
||||||
includes = sorted(set(includes))
|
includes = sorted(set(includes))
|
||||||
|
|
||||||
|
print('#pragma once')
|
||||||
print('\n'.join(includes))
|
print('\n'.join(includes))
|
||||||
print('#define SSP_DISABLE_FAST_FLOAT')
|
print('#define SSP_DISABLE_FAST_FLOAT')
|
||||||
print('\n'.join(combined_file))
|
print('\n'.join(combined_file))
|
||||||
|
@ -5,37 +5,43 @@ project(ssp_tests CXX)
|
|||||||
# ---- Dependencies ----
|
# ---- Dependencies ----
|
||||||
|
|
||||||
include(FetchContent)
|
include(FetchContent)
|
||||||
fetchcontent_declare(ssp SOURCE_DIR "${PROJECT_SOURCE_DIR}/..")
|
FetchContent_Declare(ssp SOURCE_DIR "${PROJECT_SOURCE_DIR}/..")
|
||||||
fetchcontent_makeavailable(ssp)
|
FetchContent_MakeAvailable(ssp)
|
||||||
|
|
||||||
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
|
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
|
||||||
target_compile_options(ssp INTERFACE -Wall -Wextra)
|
target_compile_options(ssp INTERFACE -Wall -Wextra)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if (MSVC)
|
||||||
|
add_compile_options(/bigobj)
|
||||||
|
elseif (MINGW)
|
||||||
|
add_compile_options(-Wa,-mbig-obj)
|
||||||
|
endif ()
|
||||||
|
|
||||||
include(FetchContent)
|
include(FetchContent)
|
||||||
fetchcontent_declare(
|
FetchContent_Declare(
|
||||||
DOCTEST
|
DOCTEST
|
||||||
GIT_REPOSITORY https://github.com/red0124/doctest
|
GIT_REPOSITORY https://github.com/red0124/doctest
|
||||||
GIT_TAG origin/master
|
GIT_TAG origin/master
|
||||||
GIT_SHALLOW TRUE
|
GIT_SHALLOW TRUE)
|
||||||
)
|
|
||||||
|
|
||||||
fetchcontent_makeavailable(DOCTEST)
|
FetchContent_MakeAvailable(DOCTEST)
|
||||||
set(DOCTEST "${FETCHCONTENT_BASE_DIR}/doctest-src")
|
set(DOCTEST "${FETCHCONTENT_BASE_DIR}/doctest-src")
|
||||||
|
|
||||||
# ---- Test ----
|
# ---- Test ----
|
||||||
|
|
||||||
enable_testing()
|
enable_testing()
|
||||||
|
|
||||||
foreach(name IN ITEMS test_splitter test_parser test_converter test_extractions)
|
foreach(name IN ITEMS test_splitter test_parser1_1 test_parser1_2
|
||||||
|
test_parser1_3 test_parser1_4 test_parser1_5
|
||||||
|
test_converter test_extractions test_parser2_1
|
||||||
|
test_parser2_2 test_parser2_3 test_parser2_4
|
||||||
|
test_parser2_5 test_parser2_6
|
||||||
|
test_extractions_without_fast_float)
|
||||||
add_executable("${name}" "${name}.cpp")
|
add_executable("${name}" "${name}.cpp")
|
||||||
target_link_libraries(
|
target_link_libraries("${name}" PRIVATE ssp::ssp fast_float
|
||||||
"${name}"
|
doctest::doctest)
|
||||||
PRIVATE ssp::ssp fast_float doctest::doctest
|
|
||||||
)
|
|
||||||
target_compile_definitions(
|
target_compile_definitions(
|
||||||
"${name}"
|
"${name}" PRIVATE DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN CMAKE_GITHUB_CI)
|
||||||
PRIVATE DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN CMAKE_GITHUB_CI
|
|
||||||
)
|
|
||||||
add_test(NAME "${name}" COMMAND "${name}")
|
add_test(NAME "${name}" COMMAND "${name}")
|
||||||
endforeach()
|
endforeach()
|
||||||
|
@ -1,19 +1,33 @@
|
|||||||
test_sources = files([
|
|
||||||
'test_main.cpp',
|
|
||||||
'test_splitter.cpp',
|
|
||||||
'test_converter.cpp',
|
|
||||||
'test_parser.cpp',
|
|
||||||
'test_extractions.cpp',
|
|
||||||
'test_extractions_without_fast_float.cpp',
|
|
||||||
])
|
|
||||||
|
|
||||||
doctest_dep = dependency('doctest')
|
doctest_dep = dependency('doctest')
|
||||||
|
add_project_arguments('-DDOCTEST_CONFIG_IMPLEMENT_WITH_MAIN', language: 'cpp')
|
||||||
|
|
||||||
test_exe = executable(
|
tests = [
|
||||||
'test_ssp',
|
'parser1_1',
|
||||||
sources: test_sources,
|
'parser1_2',
|
||||||
dependencies: [doctest_dep, ssp_dep],
|
'parser1_3',
|
||||||
cpp_args: '-lstdc++fs'
|
'parser1_4',
|
||||||
)
|
'parser1_5',
|
||||||
|
'splitter',
|
||||||
|
'converter',
|
||||||
|
'extractions',
|
||||||
|
'parser2_1',
|
||||||
|
'parser2_2',
|
||||||
|
'parser2_3',
|
||||||
|
'parser2_4',
|
||||||
|
'parser2_5',
|
||||||
|
'parser2_6',
|
||||||
|
'extractions_without_fast_float',
|
||||||
|
]
|
||||||
|
|
||||||
|
foreach name : tests
|
||||||
|
test_name = 'test_' + name
|
||||||
|
|
||||||
|
exe = executable(
|
||||||
|
test_name,
|
||||||
|
test_name + '.cpp',
|
||||||
|
dependencies: [doctest_dep, ssp_dep]
|
||||||
|
)
|
||||||
|
|
||||||
|
test(test_name, exe, timeout: 60)
|
||||||
|
endforeach
|
||||||
|
|
||||||
test('test_ssp', test_exe)
|
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
#include "test_helpers.hpp"
|
#include "test_helpers.hpp"
|
||||||
#include <algorithm>
|
|
||||||
#include <ss/converter.hpp>
|
#include <ss/converter.hpp>
|
||||||
|
|
||||||
TEST_CASE("converter test split") {
|
TEST_CASE("converter test split") {
|
||||||
ss::converter c;
|
ss::converter c;
|
||||||
for (const auto& [s, expected, delim] :
|
for (const auto& [s, expected, delim] :
|
||||||
// clang-format off
|
// clang-format off
|
||||||
{std::make_tuple("a,b,c,d", std::vector{"a", "b", "c", "d"}, ","),
|
{std::make_tuple("a,b,c,d", std::vector{"a", "b", "c", "d"}, ","),
|
||||||
{"", {}, " "},
|
{"", {}, " "},
|
||||||
{" x x x x | x ", {" x x x x ", " x "}, "|"},
|
{" x x x x | x ", {" x x x x ", " x "}, "|"},
|
||||||
{"a::b::c::d", {"a", "b", "c", "d"}, "::"},
|
{"a::b::c::d", {"a", "b", "c", "d"}, "::"},
|
||||||
{"x\t-\ty", {"x", "y"}, "\t-\t"},
|
{"x\t-\ty", {"x", "y"}, "\t-\t"},
|
||||||
{"x", {"x"}, ","}} // clang-format on
|
{"x", {"x"}, ","}}
|
||||||
|
// clang-format on
|
||||||
) {
|
) {
|
||||||
auto split = c.split(s, delim);
|
auto split = c.split(s, delim);
|
||||||
CHECK_EQ(split.size(), expected.size());
|
CHECK_EQ(split.size(), expected.size());
|
||||||
@ -22,90 +22,112 @@ TEST_CASE("converter test split") {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("converter test valid conversions") {
|
TEST_CASE("converter test split with exceptions") {
|
||||||
|
ss::converter<ss::throw_on_error> c;
|
||||||
|
try {
|
||||||
|
for (const auto& [s, expected, delim] :
|
||||||
|
// clang-format off
|
||||||
|
{std::make_tuple("a,b,c,d", std::vector{"a", "b", "c", "d"}, ","),
|
||||||
|
{"", {}, " "},
|
||||||
|
{" x x x x | x ", {" x x x x ", " x "}, "|"},
|
||||||
|
{"a::b::c::d", {"a", "b", "c", "d"}, "::"},
|
||||||
|
{"x\t-\ty", {"x", "y"}, "\t-\t"},
|
||||||
|
{"x", {"x"}, ","}} // clang-format on
|
||||||
|
) {
|
||||||
|
auto split = c.split(s, delim);
|
||||||
|
CHECK_EQ(split.size(), expected.size());
|
||||||
|
for (size_t i = 0; i < split.size(); ++i) {
|
||||||
|
auto s = std::string(split[i].first, split[i].second);
|
||||||
|
CHECK_EQ(s, expected[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (ss::exception& e) {
|
||||||
|
FAIL(std::string{e.what()});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_TEMPLATE("converter test valid conversions", T, int, ss::uint8) {
|
||||||
ss::converter c;
|
ss::converter c;
|
||||||
|
|
||||||
{
|
{
|
||||||
auto tup = c.convert<int>("5");
|
auto tup = c.convert<T>("5");
|
||||||
REQUIRE(c.valid());
|
REQUIRE(c.valid());
|
||||||
CHECK_EQ(tup, 5);
|
CHECK_EQ(tup, 5);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
auto tup = c.convert<int, void>("5,junk");
|
auto tup = c.convert<T, void>("5,junk");
|
||||||
REQUIRE(c.valid());
|
REQUIRE(c.valid());
|
||||||
CHECK_EQ(tup, 5);
|
CHECK_EQ(tup, 5);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
auto tup = c.convert<void, int>("junk,5");
|
auto tup = c.convert<void, T>("junk,5");
|
||||||
REQUIRE(c.valid());
|
REQUIRE(c.valid());
|
||||||
CHECK_EQ(tup, 5);
|
CHECK_EQ(tup, 5);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
auto tup = c.convert<int, void, void>("5\njunk\njunk", "\n");
|
auto tup = c.convert<T, void, void>("5\njunk\njunk", "\n");
|
||||||
REQUIRE(c.valid());
|
REQUIRE(c.valid());
|
||||||
CHECK_EQ(tup, 5);
|
CHECK_EQ(tup, 5);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
auto tup = c.convert<void, int, void>("junk 5 junk", " ");
|
auto tup = c.convert<void, T, void>("junk 5 junk", " ");
|
||||||
REQUIRE(c.valid());
|
REQUIRE(c.valid());
|
||||||
CHECK_EQ(tup, 5);
|
CHECK_EQ(tup, 5);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
auto tup = c.convert<void, void, int>("junk\tjunk\t5", "\t");
|
auto tup = c.convert<void, void, T>("junk\tjunk\t5", "\t");
|
||||||
REQUIRE(c.valid());
|
REQUIRE(c.valid());
|
||||||
CHECK_EQ(tup, 5);
|
CHECK_EQ(tup, 5);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
auto tup =
|
auto tup =
|
||||||
c.convert<void, void, std::optional<int>>("junk\tjunk\t5", "\t");
|
c.convert<void, void, std::optional<T>>("junk\tjunk\t5", "\t");
|
||||||
REQUIRE(c.valid());
|
REQUIRE(c.valid());
|
||||||
REQUIRE(tup.has_value());
|
REQUIRE(tup.has_value());
|
||||||
CHECK_EQ(tup, 5);
|
CHECK_EQ(tup, 5);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
auto tup = c.convert<int, double, void>("5,6.6,junk");
|
auto tup = c.convert<T, double, void>("5,6.6,junk");
|
||||||
REQUIRE(c.valid());
|
REQUIRE(c.valid());
|
||||||
CHECK_EQ(tup, std::make_tuple(5, 6.6));
|
CHECK_EQ(tup, std::make_tuple(5, 6.6));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
auto tup = c.convert<int, void, double>("5,junk,6.6");
|
auto tup = c.convert<T, void, double>("5,junk,6.6");
|
||||||
REQUIRE(c.valid());
|
REQUIRE(c.valid());
|
||||||
CHECK_EQ(tup, std::make_tuple(5, 6.6));
|
CHECK_EQ(tup, std::make_tuple(5, 6.6));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
auto tup = c.convert<void, int, double>("junk;5;6.6", ";");
|
auto tup = c.convert<void, T, double>("junk;5;6.6", ";");
|
||||||
REQUIRE(c.valid());
|
REQUIRE(c.valid());
|
||||||
CHECK_EQ(tup, std::make_tuple(5, 6.6));
|
CHECK_EQ(tup, std::make_tuple(5, 6.6));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
auto tup =
|
auto tup = c.convert<void, std::optional<T>, double>("junk;5;6.6", ";");
|
||||||
c.convert<void, std::optional<int>, double>("junk;5;6.6", ";");
|
|
||||||
REQUIRE(c.valid());
|
REQUIRE(c.valid());
|
||||||
REQUIRE(std::get<0>(tup).has_value());
|
REQUIRE(std::get<0>(tup).has_value());
|
||||||
CHECK_EQ(tup, std::make_tuple(5, 6.6));
|
CHECK_EQ(tup, std::make_tuple(5, 6.6));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
auto tup =
|
auto tup =
|
||||||
c.convert<void, std::optional<int>, double>("junk;5.4;6.6", ";");
|
c.convert<void, std::optional<T>, double>("junk;5.4;6.6", ";");
|
||||||
REQUIRE(c.valid());
|
REQUIRE(c.valid());
|
||||||
REQUIRE_FALSE(std::get<0>(tup).has_value());
|
REQUIRE_FALSE(std::get<0>(tup).has_value());
|
||||||
CHECK_EQ(tup, std::make_tuple(std::optional<int>{}, 6.6));
|
CHECK_EQ(tup, std::make_tuple(std::optional<T>{}, 6.6));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
auto tup =
|
auto tup =
|
||||||
c.convert<void, std::variant<int, double>, double>("junk;5;6.6",
|
c.convert<void, std::variant<T, double>, double>("junk;5;6.6", ";");
|
||||||
";");
|
|
||||||
REQUIRE(c.valid());
|
REQUIRE(c.valid());
|
||||||
REQUIRE(std::holds_alternative<int>(std::get<0>(tup)));
|
REQUIRE(std::holds_alternative<T>(std::get<0>(tup)));
|
||||||
CHECK_EQ(tup, std::make_tuple(std::variant<int, double>{5}, 6.6));
|
CHECK_EQ(tup, std::make_tuple(std::variant<T, double>{T(5)}, 6.6));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
auto tup =
|
auto tup =
|
||||||
c.convert<void, std::variant<int, double>, double>("junk;5.5;6.6",
|
c.convert<void, std::variant<T, double>, double>("junk;5.5;6.6",
|
||||||
";");
|
";");
|
||||||
REQUIRE(c.valid());
|
REQUIRE(c.valid());
|
||||||
REQUIRE(std::holds_alternative<double>(std::get<0>(tup)));
|
REQUIRE(std::holds_alternative<double>(std::get<0>(tup)));
|
||||||
CHECK_EQ(tup, std::make_tuple(std::variant<int, double>{5.5}, 6.6));
|
CHECK_EQ(tup, std::make_tuple(std::variant<T, double>{5.5}, 6.6));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
auto tup = c.convert<void, std::string_view, double,
|
auto tup = c.convert<void, std::string_view, double,
|
||||||
@ -116,81 +138,272 @@ TEST_CASE("converter test valid conversions") {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("converter test invalid conversions") {
|
TEST_CASE_TEMPLATE("converter test valid conversions with exceptions", T, int,
|
||||||
|
ss::uint8) {
|
||||||
|
ss::converter<ss::throw_on_error> c;
|
||||||
|
|
||||||
|
try {
|
||||||
|
auto tup = c.convert<T>("5");
|
||||||
|
REQUIRE(c.valid());
|
||||||
|
CHECK_EQ(tup, 5);
|
||||||
|
} catch (ss::exception& e) {
|
||||||
|
FAIL(std::string{e.what()});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
auto tup = c.convert<T, void>("5,junk");
|
||||||
|
REQUIRE(c.valid());
|
||||||
|
CHECK_EQ(tup, 5);
|
||||||
|
} catch (ss::exception& e) {
|
||||||
|
FAIL(std::string{e.what()});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
auto tup = c.convert<void, T>("junk,5");
|
||||||
|
REQUIRE(c.valid());
|
||||||
|
CHECK_EQ(tup, 5);
|
||||||
|
} catch (ss::exception& e) {
|
||||||
|
FAIL(std::string{e.what()});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
auto tup = c.convert<T, void, void>("5\njunk\njunk", "\n");
|
||||||
|
REQUIRE(c.valid());
|
||||||
|
CHECK_EQ(tup, 5);
|
||||||
|
} catch (ss::exception& e) {
|
||||||
|
FAIL(std::string{e.what()});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
auto tup = c.convert<void, T, void>("junk 5 junk", " ");
|
||||||
|
REQUIRE(c.valid());
|
||||||
|
CHECK_EQ(tup, 5);
|
||||||
|
} catch (ss::exception& e) {
|
||||||
|
FAIL(std::string{e.what()});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
auto tup = c.convert<void, void, T>("junk\tjunk\t5", "\t");
|
||||||
|
REQUIRE(c.valid());
|
||||||
|
CHECK_EQ(tup, 5);
|
||||||
|
} catch (ss::exception& e) {
|
||||||
|
FAIL(std::string{e.what()});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
auto tup =
|
||||||
|
c.convert<void, void, std::optional<T>>("junk\tjunk\t5", "\t");
|
||||||
|
REQUIRE(c.valid());
|
||||||
|
REQUIRE(tup.has_value());
|
||||||
|
CHECK_EQ(tup, 5);
|
||||||
|
} catch (ss::exception& e) {
|
||||||
|
FAIL(std::string{e.what()});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
auto tup = c.convert<T, double, void>("5,6.6,junk");
|
||||||
|
REQUIRE(c.valid());
|
||||||
|
CHECK_EQ(tup, std::make_tuple(5, 6.6));
|
||||||
|
} catch (ss::exception& e) {
|
||||||
|
FAIL(std::string{e.what()});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
auto tup = c.convert<T, void, double>("5,junk,6.6");
|
||||||
|
REQUIRE(c.valid());
|
||||||
|
CHECK_EQ(tup, std::make_tuple(5, 6.6));
|
||||||
|
} catch (ss::exception& e) {
|
||||||
|
FAIL(std::string{e.what()});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
auto tup = c.convert<void, T, double>("junk;5;6.6", ";");
|
||||||
|
REQUIRE(c.valid());
|
||||||
|
CHECK_EQ(tup, std::make_tuple(5, 6.6));
|
||||||
|
} catch (ss::exception& e) {
|
||||||
|
FAIL(std::string{e.what()});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
auto tup = c.convert<void, std::optional<T>, double>("junk;5;6.6", ";");
|
||||||
|
REQUIRE(c.valid());
|
||||||
|
REQUIRE(std::get<0>(tup).has_value());
|
||||||
|
CHECK_EQ(tup, std::make_tuple(5, 6.6));
|
||||||
|
} catch (ss::exception& e) {
|
||||||
|
FAIL(std::string{e.what()});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
auto tup =
|
||||||
|
c.convert<void, std::optional<T>, double>("junk;5.4;6.6", ";");
|
||||||
|
REQUIRE(c.valid());
|
||||||
|
REQUIRE_FALSE(std::get<0>(tup).has_value());
|
||||||
|
CHECK_EQ(tup, std::make_tuple(std::optional<T>{}, 6.6));
|
||||||
|
} catch (ss::exception& e) {
|
||||||
|
FAIL(std::string{e.what()});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
auto tup =
|
||||||
|
c.convert<void, std::variant<T, double>, double>("junk;5;6.6", ";");
|
||||||
|
REQUIRE(c.valid());
|
||||||
|
REQUIRE(std::holds_alternative<T>(std::get<0>(tup)));
|
||||||
|
CHECK_EQ(tup, std::make_tuple(std::variant<T, double>{T(5)}, 6.6));
|
||||||
|
} catch (ss::exception& e) {
|
||||||
|
FAIL(std::string{e.what()});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
auto tup =
|
||||||
|
c.convert<void, std::variant<T, double>, double>("junk;5.5;6.6",
|
||||||
|
";");
|
||||||
|
REQUIRE(c.valid());
|
||||||
|
REQUIRE(std::holds_alternative<double>(std::get<0>(tup)));
|
||||||
|
CHECK_EQ(tup, std::make_tuple(std::variant<T, double>{5.5}, 6.6));
|
||||||
|
} catch (ss::exception& e) {
|
||||||
|
FAIL(std::string{e.what()});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
auto tup = c.convert<void, std::string_view, double,
|
||||||
|
std::string_view>("junk;s1;6.6;s2", ";");
|
||||||
|
REQUIRE(c.valid());
|
||||||
|
CHECK_EQ(tup, std::make_tuple(std::string_view{"s1"}, 6.6,
|
||||||
|
std::string_view{"s2"}));
|
||||||
|
} catch (ss::exception& e) {
|
||||||
|
FAIL(std::string{e.what()});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_TEMPLATE("converter test invalid conversions", T, int, ss::uint8) {
|
||||||
ss::converter c;
|
ss::converter c;
|
||||||
|
|
||||||
c.convert<int>("");
|
std::ignore = c.convert<T>("");
|
||||||
REQUIRE_FALSE(c.valid());
|
REQUIRE_FALSE(c.valid());
|
||||||
|
|
||||||
c.convert<int>("10", "");
|
std::ignore = c.convert<T>("1", "");
|
||||||
REQUIRE_FALSE(c.valid());
|
REQUIRE_FALSE(c.valid());
|
||||||
|
|
||||||
c.convert<int, void>("");
|
std::ignore = c.convert<T>("10", "");
|
||||||
REQUIRE_FALSE(c.valid());
|
REQUIRE_FALSE(c.valid());
|
||||||
|
|
||||||
c.convert<int, void>(",junk");
|
std::ignore = c.convert<T, void>("");
|
||||||
REQUIRE_FALSE(c.valid());
|
REQUIRE_FALSE(c.valid());
|
||||||
|
|
||||||
c.convert<void, int>("junk,");
|
std::ignore = c.convert<T, void>(",junk");
|
||||||
REQUIRE_FALSE(c.valid());
|
REQUIRE_FALSE(c.valid());
|
||||||
|
|
||||||
c.convert<int>("x");
|
std::ignore = c.convert<void, T>("junk,");
|
||||||
REQUIRE_FALSE(c.valid());
|
REQUIRE_FALSE(c.valid());
|
||||||
|
|
||||||
c.convert<int, void>("x");
|
std::ignore = c.convert<T>("x");
|
||||||
REQUIRE_FALSE(c.valid());
|
REQUIRE_FALSE(c.valid());
|
||||||
|
|
||||||
c.convert<int, void>("x,junk");
|
std::ignore = c.convert<T, void>("x");
|
||||||
REQUIRE_FALSE(c.valid());
|
REQUIRE_FALSE(c.valid());
|
||||||
|
|
||||||
c.convert<void, int>("junk,x");
|
std::ignore = c.convert<T, void>("x,junk");
|
||||||
REQUIRE_FALSE(c.valid());
|
REQUIRE_FALSE(c.valid());
|
||||||
|
|
||||||
c.convert<void, std::variant<int, double>, double>("junk;.5.5;6", ";");
|
std::ignore = c.convert<void, T>("junk,x");
|
||||||
|
REQUIRE_FALSE(c.valid());
|
||||||
|
|
||||||
|
std::ignore =
|
||||||
|
c.convert<void, std::variant<T, double>, double>("junk;.5.5;6", ";");
|
||||||
REQUIRE_FALSE(c.valid());
|
REQUIRE_FALSE(c.valid());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("converter test ss:ax restriction (all except)") {
|
TEST_CASE_TEMPLATE("converter test invalid conversions with exceptions", T, int,
|
||||||
|
ss::uint8) {
|
||||||
|
ss::converter<ss::throw_on_error> c;
|
||||||
|
|
||||||
|
REQUIRE_EXCEPTION(std::ignore = c.convert<T>(""));
|
||||||
|
REQUIRE_EXCEPTION(std::ignore = c.convert<T>("1", ""));
|
||||||
|
REQUIRE_EXCEPTION(std::ignore = c.convert<T>("10", ""));
|
||||||
|
REQUIRE_EXCEPTION(std::ignore = c.convert<T, void>(""));
|
||||||
|
REQUIRE_EXCEPTION(std::ignore = c.convert<T, void>(",junk"));
|
||||||
|
REQUIRE_EXCEPTION(std::ignore = c.convert<void, T>("junk,"));
|
||||||
|
REQUIRE_EXCEPTION(std::ignore = c.convert<T>("x"));
|
||||||
|
REQUIRE_EXCEPTION(std::ignore = c.convert<T, void>("x"));
|
||||||
|
REQUIRE_EXCEPTION(std::ignore = c.convert<T, void>("x,junk"));
|
||||||
|
REQUIRE_EXCEPTION(std::ignore = c.convert<void, T>("junk,x"));
|
||||||
|
REQUIRE_EXCEPTION(
|
||||||
|
std::ignore =
|
||||||
|
c.convert<void, std::variant<T, double>, double>("junk;.5.5;6",
|
||||||
|
";"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_TEMPLATE("converter test ss:ax restriction (all except)", T, int,
|
||||||
|
ss::uint8) {
|
||||||
ss::converter c;
|
ss::converter c;
|
||||||
|
|
||||||
c.convert<ss::ax<int, 0>>("0");
|
std::ignore = c.convert<ss::ax<T, 0>>("0");
|
||||||
REQUIRE_FALSE(c.valid());
|
REQUIRE_FALSE(c.valid());
|
||||||
|
|
||||||
c.convert<ss::ax<int, 0, 1, 2>>("1");
|
std::ignore = c.convert<ss::ax<T, 0, 1, 2>>("1");
|
||||||
REQUIRE_FALSE(c.valid());
|
REQUIRE_FALSE(c.valid());
|
||||||
|
|
||||||
c.convert<void, char, ss::ax<int, 0, 1, 2>>("junk,c,1");
|
std::ignore = c.convert<void, char, ss::ax<T, 0, 1, 2>>("junk,c,1");
|
||||||
REQUIRE_FALSE(c.valid());
|
REQUIRE_FALSE(c.valid());
|
||||||
|
|
||||||
c.convert<ss::ax<int, 1>, char>("1,c");
|
std::ignore = c.convert<ss::ax<T, 1>, char>("1,c");
|
||||||
REQUIRE_FALSE(c.valid());
|
REQUIRE_FALSE(c.valid());
|
||||||
{
|
{
|
||||||
int tup = c.convert<ss::ax<int, 1>>("3");
|
T tup = c.convert<ss::ax<T, 1>>("3");
|
||||||
REQUIRE(c.valid());
|
REQUIRE(c.valid());
|
||||||
CHECK_EQ(tup, 3);
|
CHECK_EQ(tup, 3);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
std::tuple<char, int> tup = c.convert<char, ss::ax<int, 1>>("c,3");
|
std::tuple<char, T> tup = c.convert<char, ss::ax<T, 1>>("c,3");
|
||||||
REQUIRE(c.valid());
|
REQUIRE(c.valid());
|
||||||
CHECK_EQ(tup, std::make_tuple('c', 3));
|
CHECK_EQ(tup, std::make_tuple('c', 3));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
std::tuple<int, char> tup = c.convert<ss::ax<int, 1>, char>("3,c");
|
std::tuple<T, char> tup = c.convert<ss::ax<T, 1>, char>("3,c");
|
||||||
REQUIRE(c.valid());
|
REQUIRE(c.valid());
|
||||||
CHECK_EQ(tup, std::make_tuple(3, 'c'));
|
CHECK_EQ(tup, std::make_tuple(3, 'c'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_TEMPLATE(
|
||||||
|
"converter test ss:ax restriction (all except) with exceptions", T, int,
|
||||||
|
ss::uint8) {
|
||||||
|
ss::converter<ss::throw_on_error> c;
|
||||||
|
|
||||||
|
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::ax<T, 0>>("0"));
|
||||||
|
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::ax<T, 0, 1, 2>>("1"));
|
||||||
|
REQUIRE_EXCEPTION(
|
||||||
|
std::ignore = c.convert<void, char, ss::ax<T, 0, 1, 2>>("junk,c,1"));
|
||||||
|
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::ax<T, 1>, char>("1,c"));
|
||||||
|
|
||||||
|
try {
|
||||||
|
{
|
||||||
|
T tup = c.convert<ss::ax<T, 1>>("3");
|
||||||
|
CHECK_EQ(tup, 3);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::tuple<char, T> tup = c.convert<char, ss::ax<T, 1>>("c,3");
|
||||||
|
CHECK_EQ(tup, std::make_tuple('c', 3));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::tuple<T, char> tup = c.convert<ss::ax<T, 1>, char>("3,c");
|
||||||
|
CHECK_EQ(tup, std::make_tuple(3, 'c'));
|
||||||
|
}
|
||||||
|
} catch (ss::exception& e) {
|
||||||
|
FAIL(std::string{e.what()});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("converter test ss:nx restriction (none except)") {
|
TEST_CASE("converter test ss:nx restriction (none except)") {
|
||||||
ss::converter c;
|
ss::converter c;
|
||||||
|
|
||||||
c.convert<ss::nx<int, 1>>("3");
|
std::ignore = c.convert<ss::nx<int, 1>>("3");
|
||||||
REQUIRE_FALSE(c.valid());
|
REQUIRE_FALSE(c.valid());
|
||||||
|
|
||||||
c.convert<char, ss::nx<int, 1, 2, 69>>("c,3");
|
std::ignore = c.convert<char, ss::nx<int, 1, 2, 69>>("c,3");
|
||||||
REQUIRE_FALSE(c.valid());
|
REQUIRE_FALSE(c.valid());
|
||||||
|
|
||||||
c.convert<ss::nx<int, 1>, char>("3,c");
|
std::ignore = c.convert<ss::nx<int, 1>, char>("3,c");
|
||||||
REQUIRE_FALSE(c.valid());
|
REQUIRE_FALSE(c.valid());
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -215,53 +428,123 @@ TEST_CASE("converter test ss:nx restriction (none except)") {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("converter test ss:ir restriction (in range)") {
|
TEST_CASE("converter test ss:nx restriction (none except) with exceptions") {
|
||||||
|
ss::converter<ss::throw_on_error> c;
|
||||||
|
|
||||||
|
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::nx<int, 1>>("3"));
|
||||||
|
REQUIRE_EXCEPTION(std::ignore =
|
||||||
|
c.convert<char, ss::nx<int, 1, 2, 69>>("c,3"));
|
||||||
|
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::nx<int, 1>, char>("3,c"));
|
||||||
|
|
||||||
|
try {
|
||||||
|
{
|
||||||
|
auto tup = c.convert<ss::nx<int, 3>>("3");
|
||||||
|
REQUIRE(c.valid());
|
||||||
|
CHECK_EQ(tup, 3);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
auto tup = c.convert<ss::nx<int, 0, 1, 2>>("2");
|
||||||
|
REQUIRE(c.valid());
|
||||||
|
CHECK_EQ(tup, 2);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
auto tup = c.convert<char, void, ss::nx<int, 0, 1, 2>>("c,junk,1");
|
||||||
|
REQUIRE(c.valid());
|
||||||
|
CHECK_EQ(tup, std::make_tuple('c', 1));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
auto tup = c.convert<ss::nx<int, 1>, char>("1,c");
|
||||||
|
REQUIRE(c.valid());
|
||||||
|
CHECK_EQ(tup, std::make_tuple(1, 'c'));
|
||||||
|
}
|
||||||
|
} catch (ss::exception& e) {
|
||||||
|
FAIL(std::string{e.what()});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_TEMPLATE("converter test ss:ir restriction (in range)", T, int,
|
||||||
|
ss::uint8) {
|
||||||
ss::converter c;
|
ss::converter c;
|
||||||
|
|
||||||
c.convert<ss::ir<int, 0, 2>>("3");
|
std::ignore = c.convert<ss::ir<T, 0, 2>>("3");
|
||||||
REQUIRE_FALSE(c.valid());
|
REQUIRE_FALSE(c.valid());
|
||||||
|
|
||||||
c.convert<char, ss::ir<int, 4, 69>>("c,3");
|
std::ignore = c.convert<char, ss::ir<T, 4, 69>>("c,3");
|
||||||
REQUIRE_FALSE(c.valid());
|
REQUIRE_FALSE(c.valid());
|
||||||
|
|
||||||
c.convert<ss::ir<int, 1, 2>, char>("3,c");
|
std::ignore = c.convert<ss::ir<T, 1, 2>, char>("3,c");
|
||||||
REQUIRE_FALSE(c.valid());
|
REQUIRE_FALSE(c.valid());
|
||||||
|
|
||||||
{
|
{
|
||||||
auto tup = c.convert<ss::ir<int, 1, 5>>("3");
|
auto tup = c.convert<ss::ir<T, 1, 5>>("3");
|
||||||
REQUIRE(c.valid());
|
REQUIRE(c.valid());
|
||||||
CHECK_EQ(tup, 3);
|
CHECK_EQ(tup, 3);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
auto tup = c.convert<ss::ir<int, 0, 2>>("2");
|
auto tup = c.convert<ss::ir<T, 0, 2>>("2");
|
||||||
REQUIRE(c.valid());
|
REQUIRE(c.valid());
|
||||||
CHECK_EQ(tup, 2);
|
CHECK_EQ(tup, 2);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
auto tup = c.convert<char, void, ss::ir<int, 0, 1>>("c,junk,1");
|
auto tup = c.convert<char, void, ss::ir<T, 0, 1>>("c,junk,1");
|
||||||
REQUIRE(c.valid());
|
REQUIRE(c.valid());
|
||||||
CHECK_EQ(tup, std::make_tuple('c', 1));
|
CHECK_EQ(tup, std::make_tuple('c', 1));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
auto tup = c.convert<ss::ir<int, 1, 20>, char>("1,c");
|
auto tup = c.convert<ss::ir<T, 1, 20>, char>("1,c");
|
||||||
REQUIRE(c.valid());
|
REQUIRE(c.valid());
|
||||||
CHECK_EQ(tup, std::make_tuple(1, 'c'));
|
CHECK_EQ(tup, std::make_tuple(1, 'c'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_TEMPLATE(
|
||||||
|
"converter test ss:ir restriction (in range) with exceptions", T, int,
|
||||||
|
ss::uint8) {
|
||||||
|
ss::converter<ss::throw_on_error> c;
|
||||||
|
|
||||||
|
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::ir<T, 0, 2>>("3"));
|
||||||
|
REQUIRE_EXCEPTION(std::ignore = c.convert<char, ss::ir<T, 4, 69>>("c,3"));
|
||||||
|
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::ir<T, 1, 2>, char>("3,c"));
|
||||||
|
|
||||||
|
try {
|
||||||
|
{
|
||||||
|
auto tup = c.convert<ss::ir<T, 1, 5>>("3");
|
||||||
|
REQUIRE(c.valid());
|
||||||
|
CHECK_EQ(tup, 3);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
auto tup = c.convert<ss::ir<T, 0, 2>>("2");
|
||||||
|
REQUIRE(c.valid());
|
||||||
|
CHECK_EQ(tup, 2);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
auto tup = c.convert<char, void, ss::ir<T, 0, 1>>("c,junk,1");
|
||||||
|
REQUIRE(c.valid());
|
||||||
|
CHECK_EQ(tup, std::make_tuple('c', 1));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
auto tup = c.convert<ss::ir<T, 1, 20>, char>("1,c");
|
||||||
|
REQUIRE(c.valid());
|
||||||
|
CHECK_EQ(tup, std::make_tuple(1, 'c'));
|
||||||
|
}
|
||||||
|
} catch (ss::exception& e) {
|
||||||
|
FAIL(std::string{e.what()});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("converter test ss:oor restriction (out of range)") {
|
TEST_CASE("converter test ss:oor restriction (out of range)") {
|
||||||
ss::converter c;
|
ss::converter c;
|
||||||
|
|
||||||
c.convert<ss::oor<int, 1, 5>>("3");
|
std::ignore = c.convert<ss::oor<int, 1, 5>>("3");
|
||||||
REQUIRE_FALSE(c.valid());
|
REQUIRE_FALSE(c.valid());
|
||||||
|
|
||||||
c.convert<ss::oor<int, 0, 2>>("2");
|
std::ignore = c.convert<ss::oor<int, 0, 2>>("2");
|
||||||
REQUIRE_FALSE(c.valid());
|
REQUIRE_FALSE(c.valid());
|
||||||
|
|
||||||
c.convert<char, ss::oor<int, 0, 1>, void>("c,1,junk");
|
std::ignore = c.convert<char, ss::oor<int, 0, 1>, void>("c,1,junk");
|
||||||
REQUIRE_FALSE(c.valid());
|
REQUIRE_FALSE(c.valid());
|
||||||
|
|
||||||
c.convert<ss::oor<int, 1, 20>, char>("1,c");
|
std::ignore = c.convert<ss::oor<int, 1, 20>, char>("1,c");
|
||||||
REQUIRE_FALSE(c.valid());
|
REQUIRE_FALSE(c.valid());
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -283,7 +566,40 @@ TEST_CASE("converter test ss:oor restriction (out of range)") {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<int> extracted_vector = {1, 2, 3};
|
TEST_CASE("converter test ss:oor restriction (out of range) with exceptions") {
|
||||||
|
ss::converter<ss::throw_on_error> c;
|
||||||
|
|
||||||
|
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::oor<int, 1, 5>>("3"));
|
||||||
|
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::oor<int, 0, 2>>("2"));
|
||||||
|
REQUIRE_EXCEPTION(
|
||||||
|
std::ignore = c.convert<char, ss::oor<int, 0, 1>, void>("c,1,junk"));
|
||||||
|
REQUIRE_EXCEPTION(std::ignore =
|
||||||
|
c.convert<ss::oor<int, 1, 20>, char>("1,c"));
|
||||||
|
|
||||||
|
try {
|
||||||
|
{
|
||||||
|
auto tup = c.convert<ss::oor<int, 0, 2>>("3");
|
||||||
|
REQUIRE(c.valid());
|
||||||
|
CHECK_EQ(tup, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto tup = c.convert<char, void, ss::oor<int, 4, 69>>("c,junk,3");
|
||||||
|
REQUIRE(c.valid());
|
||||||
|
CHECK_EQ(tup, std::make_tuple('c', 3));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto tup = c.convert<ss::oor<int, 1, 2>, char>("3,c");
|
||||||
|
REQUIRE(c.valid());
|
||||||
|
CHECK_EQ(tup, std::make_tuple(3, 'c'));
|
||||||
|
}
|
||||||
|
} catch (ss::exception& e) {
|
||||||
|
FAIL(std::string{e.what()});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const inline std::vector<int> extracted_vector = {1, 2, 3};
|
||||||
|
|
||||||
// custom extract
|
// custom extract
|
||||||
template <>
|
template <>
|
||||||
@ -299,19 +615,19 @@ inline bool ss::extract(const char* begin, const char* end,
|
|||||||
TEST_CASE("converter test ss:ne restriction (not empty)") {
|
TEST_CASE("converter test ss:ne restriction (not empty)") {
|
||||||
ss::converter c;
|
ss::converter c;
|
||||||
|
|
||||||
c.convert<ss::ne<std::string>>("");
|
std::ignore = c.convert<ss::ne<std::string>>("");
|
||||||
REQUIRE_FALSE(c.valid());
|
REQUIRE_FALSE(c.valid());
|
||||||
|
|
||||||
c.convert<int, ss::ne<std::string>>("3,");
|
std::ignore = c.convert<int, ss::ne<std::string>>("3,");
|
||||||
REQUIRE_FALSE(c.valid());
|
REQUIRE_FALSE(c.valid());
|
||||||
|
|
||||||
c.convert<ss::ne<std::string>, int>(",3");
|
std::ignore = c.convert<ss::ne<std::string>, int>(",3");
|
||||||
REQUIRE_FALSE(c.valid());
|
REQUIRE_FALSE(c.valid());
|
||||||
|
|
||||||
c.convert<void, ss::ne<std::string>, int>("junk,,3");
|
std::ignore = c.convert<void, ss::ne<std::string>, int>("junk,,3");
|
||||||
REQUIRE_FALSE(c.valid());
|
REQUIRE_FALSE(c.valid());
|
||||||
|
|
||||||
c.convert<ss::ne<std::vector<int>>>("");
|
std::ignore = c.convert<ss::ne<std::vector<int>>>("");
|
||||||
REQUIRE_FALSE(c.valid());
|
REQUIRE_FALSE(c.valid());
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -331,26 +647,58 @@ TEST_CASE("converter test ss:ne restriction (not empty)") {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("converter test ss:ne restriction (not empty) with exceptions") {
|
||||||
|
ss::converter<ss::throw_on_error> c;
|
||||||
|
|
||||||
|
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::ne<std::string>>(""));
|
||||||
|
REQUIRE_EXCEPTION(std::ignore = c.convert<int, ss::ne<std::string>>("3,"));
|
||||||
|
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::ne<std::string>, int>(",3"));
|
||||||
|
REQUIRE_EXCEPTION(std::ignore =
|
||||||
|
c.convert<void, ss::ne<std::string>, int>("junk,,3"));
|
||||||
|
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::ne<std::vector<int>>>(""));
|
||||||
|
|
||||||
|
try {
|
||||||
|
{
|
||||||
|
auto tup = c.convert<ss::ne<std::string>>("s");
|
||||||
|
REQUIRE(c.valid());
|
||||||
|
CHECK_EQ(tup, "s");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
auto tup =
|
||||||
|
c.convert<std::optional<int>, ss::ne<std::string>>("1,s");
|
||||||
|
REQUIRE(c.valid());
|
||||||
|
CHECK_EQ(tup, std::make_tuple(1, "s"));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
auto tup = c.convert<ss::ne<std::vector<int>>>("{1 2 3}");
|
||||||
|
REQUIRE(c.valid());
|
||||||
|
CHECK_EQ(tup, extracted_vector);
|
||||||
|
}
|
||||||
|
} catch (ss::exception& e) {
|
||||||
|
FAIL(std::string{e.what()});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE(
|
TEST_CASE(
|
||||||
"converter test ss:lt ss::lte ss::gt ss::gte restriction (in range)") {
|
"converter test ss:lt ss::lte ss::gt ss::gte restriction (in range)") {
|
||||||
ss::converter c;
|
ss::converter c;
|
||||||
|
|
||||||
c.convert<ss::lt<int, 3>>("3");
|
std::ignore = c.convert<ss::lt<int, 3>>("3");
|
||||||
REQUIRE_FALSE(c.valid());
|
REQUIRE_FALSE(c.valid());
|
||||||
|
|
||||||
c.convert<ss::lt<int, 2>>("3");
|
std::ignore = c.convert<ss::lt<int, 2>>("3");
|
||||||
REQUIRE_FALSE(c.valid());
|
REQUIRE_FALSE(c.valid());
|
||||||
|
|
||||||
c.convert<ss::gt<int, 3>>("3");
|
std::ignore = c.convert<ss::gt<int, 3>>("3");
|
||||||
REQUIRE_FALSE(c.valid());
|
REQUIRE_FALSE(c.valid());
|
||||||
|
|
||||||
c.convert<ss::gt<int, 4>>("3");
|
std::ignore = c.convert<ss::gt<int, 4>>("3");
|
||||||
REQUIRE_FALSE(c.valid());
|
REQUIRE_FALSE(c.valid());
|
||||||
|
|
||||||
c.convert<ss::lte<int, 2>>("3");
|
std::ignore = c.convert<ss::lte<int, 2>>("3");
|
||||||
REQUIRE_FALSE(c.valid());
|
REQUIRE_FALSE(c.valid());
|
||||||
|
|
||||||
c.convert<ss::gte<int, 4>>("3");
|
std::ignore = c.convert<ss::gte<int, 4>>("3");
|
||||||
REQUIRE_FALSE(c.valid());
|
REQUIRE_FALSE(c.valid());
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -390,13 +738,70 @@ TEST_CASE(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("converter test ss:lt ss::lte ss::gt ss::gte restriction (in range) "
|
||||||
|
"with exception") {
|
||||||
|
ss::converter<ss::throw_on_error> c;
|
||||||
|
|
||||||
|
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::lt<int, 3>>("3"));
|
||||||
|
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::lt<int, 2>>("3"));
|
||||||
|
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::gt<int, 3>>("3"));
|
||||||
|
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::gt<int, 4>>("3"));
|
||||||
|
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::lte<int, 2>>("3"));
|
||||||
|
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::gte<int, 4>>("3"));
|
||||||
|
|
||||||
|
try {
|
||||||
|
{
|
||||||
|
auto tup = c.convert<ss::lt<int, 4>>("3");
|
||||||
|
REQUIRE(c.valid());
|
||||||
|
CHECK_EQ(tup, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto tup = c.convert<ss::gt<int, 2>>("3");
|
||||||
|
REQUIRE(c.valid());
|
||||||
|
CHECK_EQ(tup, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto tup = c.convert<ss::lte<int, 4>>("3");
|
||||||
|
REQUIRE(c.valid());
|
||||||
|
CHECK_EQ(tup, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto tup = c.convert<ss::lte<int, 3>>("3");
|
||||||
|
REQUIRE(c.valid());
|
||||||
|
CHECK_EQ(tup, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto tup = c.convert<ss::gte<int, 2>>("3");
|
||||||
|
REQUIRE(c.valid());
|
||||||
|
CHECK_EQ(tup, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto tup = c.convert<ss::gte<int, 3>>("3");
|
||||||
|
REQUIRE(c.valid());
|
||||||
|
CHECK_EQ(tup, 3);
|
||||||
|
}
|
||||||
|
} catch (ss::exception& e) {
|
||||||
|
FAIL(std::string{e.what()});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("converter test error mode") {
|
TEST_CASE("converter test error mode") {
|
||||||
ss::converter<ss::string_error> c;
|
ss::converter<ss::string_error> c;
|
||||||
c.convert<int>("junk");
|
std::ignore = c.convert<int>("junk");
|
||||||
CHECK_FALSE(c.valid());
|
CHECK_FALSE(c.valid());
|
||||||
CHECK_FALSE(c.error_msg().empty());
|
CHECK_FALSE(c.error_msg().empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("converter test throw on error mode") {
|
||||||
|
ss::converter<ss::throw_on_error> c;
|
||||||
|
REQUIRE_EXCEPTION(std::ignore = c.convert<int>("junk"));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("converter test converter with quotes spacing and escaping") {
|
TEST_CASE("converter test converter with quotes spacing and escaping") {
|
||||||
{
|
{
|
||||||
ss::converter c;
|
ss::converter c;
|
||||||
@ -444,6 +849,66 @@ TEST_CASE("converter test converter with quotes spacing and escaping") {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("converter test converter with quotes spacing and escaping with "
|
||||||
|
"exceptions") {
|
||||||
|
try {
|
||||||
|
ss::converter<ss::throw_on_error> c;
|
||||||
|
|
||||||
|
auto tup = c.convert<std::string, std::string, std::string>(
|
||||||
|
R"("just","some","strings")");
|
||||||
|
REQUIRE(c.valid());
|
||||||
|
CHECK_EQ(tup, std::make_tuple("\"just\"", "\"some\"", "\"strings\""));
|
||||||
|
} catch (ss::exception& e) {
|
||||||
|
FAIL(std::string{e.what()});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
ss::converter<ss::quote<'"'>> c;
|
||||||
|
|
||||||
|
auto tup = c.convert<std::string, std::string, double, char>(
|
||||||
|
buff(R"("just",some,"12.3","a")"));
|
||||||
|
REQUIRE(c.valid());
|
||||||
|
CHECK_EQ(tup, std::make_tuple("just", "some", 12.3, 'a'));
|
||||||
|
} catch (ss::exception& e) {
|
||||||
|
FAIL(std::string{e.what()});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
ss::converter<ss::throw_on_error, ss::trim<' '>> c;
|
||||||
|
|
||||||
|
auto tup = c.convert<std::string, std::string, double, char>(
|
||||||
|
buff(R"( just , some , 12.3 ,a )"));
|
||||||
|
REQUIRE(c.valid());
|
||||||
|
CHECK_EQ(tup, std::make_tuple("just", "some", 12.3, 'a'));
|
||||||
|
} catch (ss::exception& e) {
|
||||||
|
FAIL(std::string{e.what()});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
ss::converter<ss::throw_on_error, ss::escape<'\\'>> c;
|
||||||
|
|
||||||
|
auto tup =
|
||||||
|
c.convert<std::string, std::string>(buff(R"(ju\,st,strings)"));
|
||||||
|
REQUIRE(c.valid());
|
||||||
|
CHECK_EQ(tup, std::make_tuple("ju,st", "strings"));
|
||||||
|
} catch (ss::exception& e) {
|
||||||
|
FAIL(std::string{e.what()});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
ss::converter<ss::throw_on_error, ss::escape<'\\'>, ss::trim<' '>,
|
||||||
|
ss::quote<'"'>>
|
||||||
|
c;
|
||||||
|
|
||||||
|
auto tup = c.convert<std::string, std::string, double, std::string>(
|
||||||
|
buff(R"( ju\,st , "so,me" , 12.34 , "str""ings")"));
|
||||||
|
REQUIRE(c.valid());
|
||||||
|
CHECK_EQ(tup, std::make_tuple("ju,st", "so,me", 12.34, "str\"ings"));
|
||||||
|
} catch (ss::exception& e) {
|
||||||
|
FAIL(std::string{e.what()});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("converter test invalid split conversions") {
|
TEST_CASE("converter test invalid split conversions") {
|
||||||
ss::converter<ss::string_error, ss::escape<'\\'>, ss::trim<' '>,
|
ss::converter<ss::string_error, ss::escape<'\\'>, ss::trim<' '>,
|
||||||
ss::quote<'"'>>
|
ss::quote<'"'>>
|
||||||
@ -451,7 +916,7 @@ TEST_CASE("converter test invalid split conversions") {
|
|||||||
|
|
||||||
{
|
{
|
||||||
// mismatched quote
|
// mismatched quote
|
||||||
auto tup = c.convert<std::string, std::string, double, char>(
|
std::ignore = c.convert<std::string, std::string, double, char>(
|
||||||
buff(R"( "just , some , "12.3","a" )"));
|
buff(R"( "just , some , "12.3","a" )"));
|
||||||
CHECK_FALSE(c.valid());
|
CHECK_FALSE(c.valid());
|
||||||
CHECK_FALSE(c.unterminated_quote());
|
CHECK_FALSE(c.unterminated_quote());
|
||||||
@ -460,7 +925,7 @@ TEST_CASE("converter test invalid split conversions") {
|
|||||||
|
|
||||||
{
|
{
|
||||||
// unterminated quote
|
// unterminated quote
|
||||||
auto tup = c.convert<std::string, std::string, double, std::string>(
|
std::ignore = c.convert<std::string, std::string, double, std::string>(
|
||||||
buff(R"( ju\,st , "so,me" , 12.34 , "str""ings)"));
|
buff(R"( ju\,st , "so,me" , 12.34 , "str""ings)"));
|
||||||
CHECK_FALSE(c.valid());
|
CHECK_FALSE(c.valid());
|
||||||
CHECK(c.unterminated_quote());
|
CHECK(c.unterminated_quote());
|
||||||
@ -469,7 +934,7 @@ TEST_CASE("converter test invalid split conversions") {
|
|||||||
|
|
||||||
{
|
{
|
||||||
// unterminated escape
|
// unterminated escape
|
||||||
auto tup = c.convert<std::string, std::string, double, std::string>(
|
std::ignore = c.convert<std::string, std::string, double, std::string>(
|
||||||
buff(R"(just,some,2,strings\)"));
|
buff(R"(just,some,2,strings\)"));
|
||||||
CHECK_FALSE(c.valid());
|
CHECK_FALSE(c.valid());
|
||||||
CHECK_FALSE(c.unterminated_quote());
|
CHECK_FALSE(c.unterminated_quote());
|
||||||
@ -478,7 +943,7 @@ TEST_CASE("converter test invalid split conversions") {
|
|||||||
|
|
||||||
{
|
{
|
||||||
// unterminated escape while quoting
|
// unterminated escape while quoting
|
||||||
auto tup = c.convert<std::string, std::string, double, std::string>(
|
std::ignore = c.convert<std::string, std::string, double, std::string>(
|
||||||
buff(R"(just,some,2,"strings\)"));
|
buff(R"(just,some,2,"strings\)"));
|
||||||
CHECK_FALSE(c.valid());
|
CHECK_FALSE(c.valid());
|
||||||
CHECK_FALSE(c.unterminated_quote());
|
CHECK_FALSE(c.unterminated_quote());
|
||||||
@ -487,10 +952,46 @@ TEST_CASE("converter test invalid split conversions") {
|
|||||||
|
|
||||||
{
|
{
|
||||||
// unterminated escaped quote
|
// unterminated escaped quote
|
||||||
auto tup = c.convert<std::string, std::string, double, std::string>(
|
std::ignore = c.convert<std::string, std::string, double, std::string>(
|
||||||
buff(R"(just,some,2,"strings\")"));
|
buff(R"(just,some,2,"strings\")"));
|
||||||
CHECK_FALSE(c.valid());
|
CHECK_FALSE(c.valid());
|
||||||
CHECK(c.unterminated_quote());
|
CHECK(c.unterminated_quote());
|
||||||
CHECK_FALSE(c.error_msg().empty());
|
CHECK_FALSE(c.error_msg().empty());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("converter test invalid split conversions with exceptions") {
|
||||||
|
ss::converter<ss::escape<'\\'>, ss::trim<' '>, ss::quote<'"'>,
|
||||||
|
ss::throw_on_error>
|
||||||
|
c;
|
||||||
|
|
||||||
|
// mismatched quote
|
||||||
|
REQUIRE_EXCEPTION(std::ignore =
|
||||||
|
c.convert<std::string, std::string, double, char>(
|
||||||
|
buff(R"( "just , some , "12.3","a" )")));
|
||||||
|
CHECK_FALSE(c.unterminated_quote());
|
||||||
|
|
||||||
|
// unterminated quote
|
||||||
|
REQUIRE_EXCEPTION(
|
||||||
|
std::ignore = c.convert<std::string, std::string, double, std::string>(
|
||||||
|
buff(R"( ju\,st , "so,me" , 12.34 , "str""ings)")));
|
||||||
|
CHECK(c.unterminated_quote());
|
||||||
|
|
||||||
|
// unterminated escape
|
||||||
|
REQUIRE_EXCEPTION(
|
||||||
|
std::ignore = c.convert<std::string, std::string, double, std::string>(
|
||||||
|
buff(R"(just,some,2,strings\)")));
|
||||||
|
CHECK_FALSE(c.unterminated_quote());
|
||||||
|
|
||||||
|
// unterminated escape while quoting
|
||||||
|
REQUIRE_EXCEPTION(
|
||||||
|
std::ignore = c.convert<std::string, std::string, double, std::string>(
|
||||||
|
buff(R"(just,some,2,"strings\)")));
|
||||||
|
CHECK_FALSE(c.unterminated_quote());
|
||||||
|
|
||||||
|
// unterminated escaped quote
|
||||||
|
REQUIRE_EXCEPTION(
|
||||||
|
std::ignore = c.convert<std::string, std::string, double, std::string>(
|
||||||
|
buff(R"(just,some,2,"strings\")")));
|
||||||
|
CHECK(c.unterminated_quote());
|
||||||
|
}
|
||||||
|
@ -2,15 +2,41 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <ss/extract.hpp>
|
#include <ss/extract.hpp>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct numeric_limits : public std::numeric_limits<T> {};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct numeric_limits<ss::numeric_wrapper<T>> : public std::numeric_limits<T> {
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct is_signed : public std::is_signed<T> {};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct is_signed<ss::int8> : public std::true_type {};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct is_unsigned : public std::is_unsigned<T> {};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct is_unsigned<ss::uint8> : public std::true_type {};
|
||||||
|
|
||||||
|
} /* anonymous namespace */
|
||||||
|
|
||||||
|
static_assert(is_signed<ss::int8>::value);
|
||||||
|
static_assert(is_unsigned<ss::uint8>::value);
|
||||||
|
|
||||||
TEST_CASE("testing extract functions for floating point values") {
|
TEST_CASE("testing extract functions for floating point values") {
|
||||||
CHECK_FLOATING_CONVERSION(123.456, float);
|
CHECK_FLOATING_CONVERSION(123.456, float);
|
||||||
CHECK_FLOATING_CONVERSION(123.456, double);
|
CHECK_FLOATING_CONVERSION(123.456, double);
|
||||||
|
|
||||||
CHECK_FLOATING_CONVERSION(69, float);
|
CHECK_FLOATING_CONVERSION(59, float);
|
||||||
CHECK_FLOATING_CONVERSION(69, double);
|
CHECK_FLOATING_CONVERSION(59, double);
|
||||||
|
|
||||||
CHECK_FLOATING_CONVERSION(420., float);
|
CHECK_FLOATING_CONVERSION(4210., float);
|
||||||
CHECK_FLOATING_CONVERSION(420., double);
|
CHECK_FLOATING_CONVERSION(4210., double);
|
||||||
|
|
||||||
CHECK_FLOATING_CONVERSION(0.123, float);
|
CHECK_FLOATING_CONVERSION(0.123, float);
|
||||||
CHECK_FLOATING_CONVERSION(0.123, double);
|
CHECK_FLOATING_CONVERSION(0.123, double);
|
||||||
@ -22,18 +48,18 @@ TEST_CASE("testing extract functions for floating point values") {
|
|||||||
#define CHECK_DECIMAL_CONVERSION(input, type) \
|
#define CHECK_DECIMAL_CONVERSION(input, type) \
|
||||||
{ \
|
{ \
|
||||||
std::string s = #input; \
|
std::string s = #input; \
|
||||||
auto t = ss::to_num<type>(s.c_str(), s.c_str() + s.size()); \
|
type value; \
|
||||||
REQUIRE(t.has_value()); \
|
bool valid = ss::extract(s.c_str(), s.c_str() + s.size(), value); \
|
||||||
CHECK_EQ(t.value(), type(input)); \
|
REQUIRE(valid); \
|
||||||
|
CHECK_EQ(value, type(input)); \
|
||||||
} \
|
} \
|
||||||
{ \
|
/* check negative too */ \
|
||||||
/* check negative too */ \
|
if (is_signed<type>::value) { \
|
||||||
if (std::is_signed_v<type>) { \
|
std::string s = std::string("-") + #input; \
|
||||||
auto s = std::string("-") + #input; \
|
type value; \
|
||||||
auto t = ss::to_num<type>(s.c_str(), s.c_str() + s.size()); \
|
bool valid = ss::extract(s.c_str(), s.c_str() + s.size(), value); \
|
||||||
REQUIRE(t.has_value()); \
|
REQUIRE(valid); \
|
||||||
CHECK_EQ(t.value(), type(-input)); \
|
CHECK_EQ(value, type(-input)); \
|
||||||
} \
|
|
||||||
}
|
}
|
||||||
|
|
||||||
using us = unsigned short;
|
using us = unsigned short;
|
||||||
@ -43,6 +69,8 @@ using ll = long long;
|
|||||||
using ull = unsigned long long;
|
using ull = unsigned long long;
|
||||||
|
|
||||||
TEST_CASE("extract test functions for decimal values") {
|
TEST_CASE("extract test functions for decimal values") {
|
||||||
|
CHECK_DECIMAL_CONVERSION(12, ss::int8);
|
||||||
|
CHECK_DECIMAL_CONVERSION(12, ss::uint8);
|
||||||
CHECK_DECIMAL_CONVERSION(1234, short);
|
CHECK_DECIMAL_CONVERSION(1234, short);
|
||||||
CHECK_DECIMAL_CONVERSION(1234, us);
|
CHECK_DECIMAL_CONVERSION(1234, us);
|
||||||
CHECK_DECIMAL_CONVERSION(1234, int);
|
CHECK_DECIMAL_CONVERSION(1234, int);
|
||||||
@ -54,6 +82,9 @@ TEST_CASE("extract test functions for decimal values") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("extract test functions for numbers with invalid inputs") {
|
TEST_CASE("extract test functions for numbers with invalid inputs") {
|
||||||
|
// negative unsigned value for numeric_wrapper
|
||||||
|
CHECK_INVALID_CONVERSION("-12", ss::uint8);
|
||||||
|
|
||||||
// negative unsigned value
|
// negative unsigned value
|
||||||
CHECK_INVALID_CONVERSION("-1234", ul);
|
CHECK_INVALID_CONVERSION("-1234", ul);
|
||||||
|
|
||||||
@ -70,46 +101,38 @@ TEST_CASE("extract test functions for numbers with invalid inputs") {
|
|||||||
CHECK_INVALID_CONVERSION("", int);
|
CHECK_INVALID_CONVERSION("", int);
|
||||||
}
|
}
|
||||||
|
|
||||||
#define CHECK_OUT_OF_RANGE_CONVERSION(type) \
|
TEST_CASE_TEMPLATE(
|
||||||
{ \
|
"extract test functions for numbers with out of range inputs", T, short, us,
|
||||||
std::string s = std::to_string(std::numeric_limits<type>::max()); \
|
int, ui, long, ul, ll, ull, ss::uint8) {
|
||||||
auto t = ss::to_num<type>(s.c_str(), s.c_str() + s.size()); \
|
{
|
||||||
CHECK(t.has_value()); \
|
std::string s = std::to_string(numeric_limits<T>::max());
|
||||||
for (auto& i : s) { \
|
auto t = ss::to_num<T>(s.c_str(), s.c_str() + s.size());
|
||||||
if (i != '9' && i != '.') { \
|
CHECK(t.has_value());
|
||||||
i = '9'; \
|
for (auto& i : s) {
|
||||||
break; \
|
if (i != '9' && i != '.') {
|
||||||
} \
|
i = '9';
|
||||||
} \
|
break;
|
||||||
t = ss::to_num<type>(s.c_str(), s.c_str() + s.size()); \
|
}
|
||||||
CHECK_FALSE(t.has_value()); \
|
}
|
||||||
} \
|
t = ss::to_num<T>(s.c_str(), s.c_str() + s.size());
|
||||||
{ \
|
CHECK_FALSE(t.has_value());
|
||||||
std::string s = std::to_string(std::numeric_limits<type>::min()); \
|
}
|
||||||
auto t = ss::to_num<type>(s.c_str(), s.c_str() + s.size()); \
|
{
|
||||||
CHECK(t.has_value()); \
|
std::string s = std::to_string(numeric_limits<T>::min());
|
||||||
for (auto& i : s) { \
|
auto t = ss::to_num<T>(s.c_str(), s.c_str() + s.size());
|
||||||
if (std::is_signed_v<type> && i != '9' && i != '.') { \
|
CHECK(t.has_value());
|
||||||
i = '9'; \
|
for (auto& i : s) {
|
||||||
break; \
|
if (is_signed<T>::value && i != '9' && i != '.') {
|
||||||
} else if (std::is_unsigned_v<type>) { \
|
i = '9';
|
||||||
s = "-1"; \
|
break;
|
||||||
break; \
|
} else if (is_unsigned<T>::value) {
|
||||||
} \
|
s = "-1";
|
||||||
} \
|
break;
|
||||||
t = ss::to_num<type>(s.c_str(), s.c_str() + s.size()); \
|
}
|
||||||
CHECK_FALSE(t.has_value()); \
|
}
|
||||||
|
t = ss::to_num<T>(s.c_str(), s.c_str() + s.size());
|
||||||
|
CHECK_FALSE(t.has_value());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("extract test functions for numbers with out of range inputs") {
|
|
||||||
CHECK_OUT_OF_RANGE_CONVERSION(short);
|
|
||||||
CHECK_OUT_OF_RANGE_CONVERSION(us);
|
|
||||||
CHECK_OUT_OF_RANGE_CONVERSION(int);
|
|
||||||
CHECK_OUT_OF_RANGE_CONVERSION(ui);
|
|
||||||
CHECK_OUT_OF_RANGE_CONVERSION(long);
|
|
||||||
CHECK_OUT_OF_RANGE_CONVERSION(ul);
|
|
||||||
CHECK_OUT_OF_RANGE_CONVERSION(ll);
|
|
||||||
CHECK_OUT_OF_RANGE_CONVERSION(ull);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("extract test functions for boolean values") {
|
TEST_CASE("extract test functions for boolean values") {
|
||||||
@ -142,12 +165,12 @@ TEST_CASE("extract test functions for char values") {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("extract test functions for std::optional") {
|
TEST_CASE_TEMPLATE("extract test functions for std::optional", T, int,
|
||||||
for (const auto& [i, s] :
|
ss::int8) {
|
||||||
{std::pair<std::optional<int>, std::string>{1, "1"},
|
for (const auto& [i, s] : {std::pair<std::optional<T>, std::string>{1, "1"},
|
||||||
{69, "69"},
|
{69, "69"},
|
||||||
{-4, "-4"}}) {
|
{-4, "-4"}}) {
|
||||||
std::optional<int> v;
|
std::optional<T> v;
|
||||||
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), v));
|
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), v));
|
||||||
REQUIRE(v.has_value());
|
REQUIRE(v.has_value());
|
||||||
CHECK_EQ(*v, i);
|
CHECK_EQ(*v, i);
|
||||||
@ -164,7 +187,7 @@ TEST_CASE("extract test functions for std::optional") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const std::string s : {"aa", "xxx", ""}) {
|
for (const std::string s : {"aa", "xxx", ""}) {
|
||||||
std::optional<int> v;
|
std::optional<T> v;
|
||||||
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), v));
|
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), v));
|
||||||
CHECK_FALSE(v.has_value());
|
CHECK_FALSE(v.has_value());
|
||||||
}
|
}
|
||||||
@ -176,56 +199,57 @@ TEST_CASE("extract test functions for std::optional") {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("extract test functions for std::variant") {
|
TEST_CASE_TEMPLATE("extract test functions for std::variant", T, int,
|
||||||
|
ss::uint8) {
|
||||||
{
|
{
|
||||||
std::string s = "22";
|
std::string s = "22";
|
||||||
{
|
{
|
||||||
std::variant<int, double, std::string> var;
|
std::variant<T, double, std::string> var;
|
||||||
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
|
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
|
||||||
CHECK_NOT_VARIANT(var, double);
|
CHECK_NOT_VARIANT(var, double);
|
||||||
CHECK_NOT_VARIANT(var, std::string);
|
CHECK_NOT_VARIANT(var, std::string);
|
||||||
REQUIRE_VARIANT(var, 22, int);
|
REQUIRE_VARIANT(var, 22, T);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
std::variant<double, int, std::string> var;
|
std::variant<double, T, std::string> var;
|
||||||
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
|
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
|
||||||
CHECK_NOT_VARIANT(var, int);
|
CHECK_NOT_VARIANT(var, T);
|
||||||
CHECK_NOT_VARIANT(var, std::string);
|
CHECK_NOT_VARIANT(var, std::string);
|
||||||
REQUIRE_VARIANT(var, 22, double);
|
REQUIRE_VARIANT(var, 22, double);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
std::variant<std::string, double, int> var;
|
std::variant<std::string, double, T> var;
|
||||||
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
|
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
|
||||||
CHECK_NOT_VARIANT(var, int);
|
CHECK_NOT_VARIANT(var, T);
|
||||||
CHECK_NOT_VARIANT(var, double);
|
CHECK_NOT_VARIANT(var, double);
|
||||||
REQUIRE_VARIANT(var, "22", std::string);
|
REQUIRE_VARIANT(var, "22", std::string);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
std::variant<int> var;
|
std::variant<T> var;
|
||||||
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
|
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
|
||||||
REQUIRE_VARIANT(var, 22, int);
|
REQUIRE_VARIANT(var, 22, T);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
std::string s = "22.2";
|
std::string s = "22.2";
|
||||||
{
|
{
|
||||||
std::variant<int, double, std::string> var;
|
std::variant<T, double, std::string> var;
|
||||||
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
|
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
|
||||||
CHECK_NOT_VARIANT(var, int);
|
CHECK_NOT_VARIANT(var, T);
|
||||||
CHECK_NOT_VARIANT(var, std::string);
|
CHECK_NOT_VARIANT(var, std::string);
|
||||||
REQUIRE_VARIANT(var, 22.2, double);
|
REQUIRE_VARIANT(var, 22.2, double);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
std::variant<double, int, std::string> var;
|
std::variant<double, T, std::string> var;
|
||||||
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
|
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
|
||||||
CHECK_NOT_VARIANT(var, int);
|
CHECK_NOT_VARIANT(var, T);
|
||||||
CHECK_NOT_VARIANT(var, std::string);
|
CHECK_NOT_VARIANT(var, std::string);
|
||||||
REQUIRE_VARIANT(var, 22.2, double);
|
REQUIRE_VARIANT(var, 22.2, double);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
std::variant<std::string, double, int> var;
|
std::variant<std::string, double, T> var;
|
||||||
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
|
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
|
||||||
CHECK_NOT_VARIANT(var, int);
|
CHECK_NOT_VARIANT(var, T);
|
||||||
CHECK_NOT_VARIANT(var, double);
|
CHECK_NOT_VARIANT(var, double);
|
||||||
REQUIRE_VARIANT(var, "22.2", std::string);
|
REQUIRE_VARIANT(var, "22.2", std::string);
|
||||||
}
|
}
|
||||||
@ -233,45 +257,62 @@ TEST_CASE("extract test functions for std::variant") {
|
|||||||
{
|
{
|
||||||
std::string s = "2.2.2";
|
std::string s = "2.2.2";
|
||||||
{
|
{
|
||||||
std::variant<int, double, std::string> var;
|
std::variant<T, double, std::string> var;
|
||||||
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
|
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
|
||||||
CHECK_NOT_VARIANT(var, int);
|
CHECK_NOT_VARIANT(var, T);
|
||||||
CHECK_NOT_VARIANT(var, double);
|
CHECK_NOT_VARIANT(var, double);
|
||||||
REQUIRE_VARIANT(var, "2.2.2", std::string);
|
REQUIRE_VARIANT(var, "2.2.2", std::string);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
std::variant<double, std::string, int> var;
|
std::variant<double, std::string, T> var;
|
||||||
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
|
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
|
||||||
CHECK_NOT_VARIANT(var, int);
|
CHECK_NOT_VARIANT(var, T);
|
||||||
CHECK_NOT_VARIANT(var, double);
|
CHECK_NOT_VARIANT(var, double);
|
||||||
REQUIRE_VARIANT(var, "2.2.2", std::string);
|
REQUIRE_VARIANT(var, "2.2.2", std::string);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
std::variant<std::string, double, int> var;
|
std::variant<std::string, double, T> var;
|
||||||
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
|
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
|
||||||
CHECK_NOT_VARIANT(var, int);
|
CHECK_NOT_VARIANT(var, T);
|
||||||
CHECK_NOT_VARIANT(var, double);
|
CHECK_NOT_VARIANT(var, double);
|
||||||
REQUIRE_VARIANT(var, "2.2.2", std::string);
|
REQUIRE_VARIANT(var, "2.2.2", std::string);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
std::variant<int, double> var;
|
std::variant<T, double> var;
|
||||||
REQUIRE_FALSE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
|
REQUIRE_FALSE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
|
||||||
|
|
||||||
REQUIRE_VARIANT(var, int{}, int);
|
REQUIRE_VARIANT(var, T{}, T);
|
||||||
CHECK_NOT_VARIANT(var, double);
|
CHECK_NOT_VARIANT(var, double);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
std::variant<double, int> var;
|
std::variant<double, T> var;
|
||||||
REQUIRE_FALSE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
|
REQUIRE_FALSE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
|
||||||
|
|
||||||
REQUIRE_VARIANT(var, double{}, double);
|
REQUIRE_VARIANT(var, double{}, double);
|
||||||
CHECK_NOT_VARIANT(var, int);
|
CHECK_NOT_VARIANT(var, T);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
std::variant<int> var;
|
std::variant<T> var;
|
||||||
REQUIRE_FALSE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
|
REQUIRE_FALSE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
|
||||||
|
|
||||||
REQUIRE_VARIANT(var, int{}, int);
|
REQUIRE_VARIANT(var, T{}, T);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("extract test with long number string") {
|
||||||
|
{
|
||||||
|
std::string string_num =
|
||||||
|
std::string(20, '1') + "." + std::string(20, '2');
|
||||||
|
|
||||||
|
CHECK_FLOATING_CONVERSION_LONG_NUMBER(string_num, float, stof);
|
||||||
|
CHECK_FLOATING_CONVERSION_LONG_NUMBER(string_num, double, stod);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::string string_num =
|
||||||
|
std::string(50, '1') + "." + std::string(50, '2');
|
||||||
|
|
||||||
|
CHECK_FLOATING_CONVERSION_LONG_NUMBER(string_num, double, stod);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
#include "test_helpers.hpp"
|
#include "test_helpers.hpp"
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
#define SSP_DISABLE_FAST_FLOAT
|
#define SSP_DISABLE_FAST_FLOAT
|
||||||
#include <ss/extract.hpp>
|
#include <ss/extract.hpp>
|
||||||
@ -9,11 +8,11 @@ TEST_CASE(
|
|||||||
CHECK_FLOATING_CONVERSION(123.456, float);
|
CHECK_FLOATING_CONVERSION(123.456, float);
|
||||||
CHECK_FLOATING_CONVERSION(123.456, double);
|
CHECK_FLOATING_CONVERSION(123.456, double);
|
||||||
|
|
||||||
CHECK_FLOATING_CONVERSION(69, float);
|
CHECK_FLOATING_CONVERSION(59, float);
|
||||||
CHECK_FLOATING_CONVERSION(69, double);
|
CHECK_FLOATING_CONVERSION(59, double);
|
||||||
|
|
||||||
CHECK_FLOATING_CONVERSION(420., float);
|
CHECK_FLOATING_CONVERSION(4210., float);
|
||||||
CHECK_FLOATING_CONVERSION(420., double);
|
CHECK_FLOATING_CONVERSION(4210., double);
|
||||||
|
|
||||||
CHECK_FLOATING_CONVERSION(0.123, float);
|
CHECK_FLOATING_CONVERSION(0.123, float);
|
||||||
CHECK_FLOATING_CONVERSION(0.123, double);
|
CHECK_FLOATING_CONVERSION(0.123, double);
|
||||||
@ -130,3 +129,20 @@ TEST_CASE("extract test functions for std::variant without fast float") {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("extract test with long number string without fast float") {
|
||||||
|
{
|
||||||
|
std::string string_num =
|
||||||
|
std::string(20, '1') + "." + std::string(20, '2');
|
||||||
|
|
||||||
|
CHECK_FLOATING_CONVERSION_LONG_NUMBER(string_num, float, stof);
|
||||||
|
CHECK_FLOATING_CONVERSION_LONG_NUMBER(string_num, double, stod);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::string string_num =
|
||||||
|
std::string(50, '1') + "." + std::string(50, '2');
|
||||||
|
|
||||||
|
CHECK_FLOATING_CONVERSION_LONG_NUMBER(string_num, double, stod);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,6 +1,14 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <cstdlib>
|
#include <algorithm>
|
||||||
#include <cstring>
|
#include <ctime>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <ss/common.hpp>
|
||||||
|
#include <ss/setup.hpp>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#ifdef CMAKE_GITHUB_CI
|
#ifdef CMAKE_GITHUB_CI
|
||||||
#include <doctest/doctest.h>
|
#include <doctest/doctest.h>
|
||||||
@ -8,45 +16,82 @@
|
|||||||
#include <doctest.h>
|
#include <doctest.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
namespace ss {
|
||||||
|
template <typename... Ts>
|
||||||
|
class parser;
|
||||||
|
} /* namespace ss */
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
struct bool_error {};
|
||||||
|
|
||||||
|
template <typename T, typename U = bool_error>
|
||||||
|
struct config {
|
||||||
|
using BufferMode = T;
|
||||||
|
using ErrorMode = U;
|
||||||
|
|
||||||
|
constexpr static auto ThrowOnError = std::is_same_v<U, ss::throw_on_error>;
|
||||||
|
constexpr static auto StringError = std::is_same_v<U, ss::string_error>;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define ParserOptionCombinations \
|
||||||
|
config<std::true_type>, config<std::true_type, ss::string_error>, \
|
||||||
|
config<std::true_type, ss::throw_on_error>, config<std::false_type>, \
|
||||||
|
config<std::false_type, ss::string_error>, \
|
||||||
|
config<std::false_type, ss::throw_on_error>
|
||||||
|
|
||||||
struct buffer {
|
struct buffer {
|
||||||
char* data_{nullptr};
|
std::string data_;
|
||||||
|
|
||||||
char* operator()(const char* data) {
|
char* operator()(const std::string& data) {
|
||||||
if (data_) {
|
data_ = data;
|
||||||
delete[] data_;
|
return data_.data();
|
||||||
}
|
|
||||||
data_ = new char[strlen(data) + 1];
|
|
||||||
strcpy(data_, data);
|
|
||||||
return data_;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
char* append(const char* data) {
|
char* append(const std::string& data) {
|
||||||
if (data_) {
|
data_ += data;
|
||||||
char* new_data_ = new char[strlen(data_) + strlen(data) + 1];
|
return data_.data();
|
||||||
strcpy(new_data_, data_);
|
|
||||||
strcat(new_data_, data);
|
|
||||||
delete[] data_;
|
|
||||||
data_ = new_data_;
|
|
||||||
return data_;
|
|
||||||
} else {
|
|
||||||
return operator()(data);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
char* append_overwrite_last(const char* data, size_t size) {
|
char* append_overwrite_last(const std::string& data, size_t size) {
|
||||||
data_[strlen(data_) - size] = '\0';
|
data_.resize(data_.size() - size);
|
||||||
return append(data);
|
return append(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
~buffer() {
|
|
||||||
if (data_) {
|
|
||||||
delete[] data_;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
[[maybe_unused]] inline buffer buff;
|
[[maybe_unused]] inline buffer buff;
|
||||||
|
|
||||||
|
[[maybe_unused]] std::string time_now_rand() {
|
||||||
|
std::srand(std::time(nullptr));
|
||||||
|
std::stringstream ss;
|
||||||
|
auto t = std::time(nullptr);
|
||||||
|
auto tm = *std::localtime(&t);
|
||||||
|
ss << std::put_time(&tm, "%d%m%Y%H%M%S");
|
||||||
|
std::srand(std::time(nullptr));
|
||||||
|
return ss.str() + std::to_string(rand());
|
||||||
|
}
|
||||||
|
|
||||||
|
struct unique_file_name {
|
||||||
|
static inline int i = 0;
|
||||||
|
|
||||||
|
std::string name;
|
||||||
|
|
||||||
|
unique_file_name(const std::string& test) {
|
||||||
|
do {
|
||||||
|
name = "ssp_test_" + test + "_" + std::to_string(i++) + "_" +
|
||||||
|
time_now_rand() + "_file.csv";
|
||||||
|
} while (std::filesystem::exists(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
~unique_file_name() {
|
||||||
|
try {
|
||||||
|
std::filesystem::remove(name);
|
||||||
|
} catch (const std::filesystem::filesystem_error& e) {
|
||||||
|
std::cerr << e.what() << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
#define CHECK_FLOATING_CONVERSION(input, type) \
|
#define CHECK_FLOATING_CONVERSION(input, type) \
|
||||||
{ \
|
{ \
|
||||||
auto eps = std::numeric_limits<type>::min(); \
|
auto eps = std::numeric_limits<type>::min(); \
|
||||||
@ -64,6 +109,18 @@ struct buffer {
|
|||||||
CHECK_LT(std::abs(t.value() - type(-input)), eps); \
|
CHECK_LT(std::abs(t.value() - type(-input)), eps); \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define CHECK_FLOATING_CONVERSION_LONG_NUMBER(STRING_NUMBER, TYPE, CONVERTER) \
|
||||||
|
{ \
|
||||||
|
auto begin = STRING_NUMBER.c_str(); \
|
||||||
|
auto end = begin + STRING_NUMBER.size(); \
|
||||||
|
\
|
||||||
|
auto number = ss::to_num<TYPE>(begin, end); \
|
||||||
|
REQUIRE(number.has_value()); \
|
||||||
|
\
|
||||||
|
auto expected_number = CONVERTER(STRING_NUMBER); \
|
||||||
|
CHECK_EQ(number.value(), expected_number); \
|
||||||
|
}
|
||||||
|
|
||||||
#define CHECK_INVALID_CONVERSION(input, type) \
|
#define CHECK_INVALID_CONVERSION(input, type) \
|
||||||
{ \
|
{ \
|
||||||
std::string s = input; \
|
std::string s = input; \
|
||||||
@ -79,3 +136,120 @@ struct buffer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#define CHECK_NOT_VARIANT(var, type) CHECK(!std::holds_alternative<type>(var));
|
#define CHECK_NOT_VARIANT(var, type) CHECK(!std::holds_alternative<type>(var));
|
||||||
|
|
||||||
|
#define REQUIRE_EXCEPTION(...) \
|
||||||
|
try { \
|
||||||
|
__VA_ARGS__; \
|
||||||
|
FAIL("Expected exception"); \
|
||||||
|
} catch (ss::exception & e) { \
|
||||||
|
CHECK_FALSE(std::string{e.what()}.empty()); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define CHECK_EQ_ARRAY(first, second) \
|
||||||
|
{ \
|
||||||
|
const auto& first_ = (first); \
|
||||||
|
const auto& second_ = (second); \
|
||||||
|
CHECK_EQ(first_.size(), second_.size()); \
|
||||||
|
for (size_t i_ = 0; i_ < std::min(first_.size(), second_.size()); \
|
||||||
|
++i_) { \
|
||||||
|
CHECK_EQ(first_[i_], second_[i_]); \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
[[maybe_unused]] std::vector<std::vector<T>> vector_combinations(
|
||||||
|
const std::vector<T>& v, size_t n) {
|
||||||
|
std::vector<std::vector<T>> ret;
|
||||||
|
if (n <= 1) {
|
||||||
|
for (const auto& i : v) {
|
||||||
|
ret.push_back({i});
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto inner_combinations = vector_combinations(v, n - 1);
|
||||||
|
for (const auto& i : v) {
|
||||||
|
for (auto j : inner_combinations) {
|
||||||
|
j.insert(j.begin(), i);
|
||||||
|
ret.push_back(std::move(j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[maybe_unused]] std::string merge_header(
|
||||||
|
const std::vector<std::string>& header,
|
||||||
|
const std::string& delimiter = ss::default_delimiter) {
|
||||||
|
std::string s;
|
||||||
|
if (!header.empty()) {
|
||||||
|
for (const auto& i : header) {
|
||||||
|
s.append(i);
|
||||||
|
s.append(delimiter);
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < delimiter.size(); ++i) {
|
||||||
|
s.pop_back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[maybe_unused]] std::string make_buffer(const std::string& file_name) {
|
||||||
|
std::ifstream in{file_name, std::ios::binary};
|
||||||
|
std::string tmp;
|
||||||
|
std::string out;
|
||||||
|
|
||||||
|
auto copy_if_whitespaces = [&] {
|
||||||
|
std::string matches = "\n\r\t ";
|
||||||
|
while (std::any_of(matches.begin(), matches.end(),
|
||||||
|
[&](auto c) { return in.peek() == c; })) {
|
||||||
|
if (in.peek() == '\r') {
|
||||||
|
out += "\r\n";
|
||||||
|
in.ignore(2);
|
||||||
|
} else {
|
||||||
|
out += std::string{static_cast<char>(in.peek())};
|
||||||
|
in.ignore(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Evade small string optimization
|
||||||
|
out.reserve(sizeof(out) + 1);
|
||||||
|
|
||||||
|
copy_if_whitespaces();
|
||||||
|
while (in >> tmp) {
|
||||||
|
out += tmp;
|
||||||
|
copy_if_whitespaces();
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <bool buffer_mode, typename... Ts>
|
||||||
|
std::tuple<ss::parser<Ts...>, std::string> make_parser_impl(
|
||||||
|
const std::string& file_name, std::string delim = ss::default_delimiter) {
|
||||||
|
if (buffer_mode) {
|
||||||
|
auto buffer = make_buffer(file_name);
|
||||||
|
return {ss::parser<Ts...>{buffer.data(), buffer.size(), delim},
|
||||||
|
std::move(buffer)};
|
||||||
|
} else {
|
||||||
|
return {ss::parser<Ts...>{file_name, delim}, std::string{}};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <bool buffer_mode, typename ErrorMode, typename... Ts>
|
||||||
|
[[maybe_unused]] std::enable_if_t<
|
||||||
|
!std::is_same_v<ErrorMode, bool_error>,
|
||||||
|
std::tuple<ss::parser<ErrorMode, Ts...>, std::string>>
|
||||||
|
make_parser(const std::string& file_name,
|
||||||
|
std::string delim = ss::default_delimiter) {
|
||||||
|
return make_parser_impl<buffer_mode, ErrorMode, Ts...>(file_name, delim);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <bool buffer_mode, typename ErrorMode, typename... Ts>
|
||||||
|
[[maybe_unused]] std::enable_if_t<std::is_same_v<ErrorMode, bool_error>,
|
||||||
|
std::tuple<ss::parser<Ts...>, std::string>>
|
||||||
|
make_parser(const std::string& file_name,
|
||||||
|
std::string delim = ss::default_delimiter) {
|
||||||
|
return make_parser_impl<buffer_mode, Ts...>(file_name, delim);
|
||||||
|
}
|
||||||
|
|
||||||
|
} /* anonymous namespace */
|
||||||
|
1137
test/test_parser.cpp
1137
test/test_parser.cpp
File diff suppressed because it is too large
Load Diff
116
test/test_parser1.hpp
Normal file
116
test/test_parser1.hpp
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "test_helpers.hpp"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <iostream>
|
||||||
|
#include <ss/parser.hpp>
|
||||||
|
#include <sstream>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
#ifdef _WIN32
|
||||||
|
void replace_all(std::string& s, const std::string& from,
|
||||||
|
const std::string& to) {
|
||||||
|
if (from.empty()) return;
|
||||||
|
size_t start_pos = 0;
|
||||||
|
while ((start_pos = s.find(from, start_pos)) != std::string::npos) {
|
||||||
|
s.replace(start_pos, from.length(), to);
|
||||||
|
start_pos += to.length();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
template <typename... Ts>
|
||||||
|
void expect_error_on_command(ss::parser<Ts...>& p,
|
||||||
|
const std::function<void()> command) {
|
||||||
|
if (ss::setup<Ts...>::throw_on_error) {
|
||||||
|
try {
|
||||||
|
command();
|
||||||
|
FAIL("expected exception");
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
CHECK_FALSE(std::string{e.what()}.empty());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
command();
|
||||||
|
CHECK(!p.valid());
|
||||||
|
if constexpr (ss::setup<Ts...>::string_error) {
|
||||||
|
CHECK_FALSE(p.error_msg().empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[maybe_unused]] void update_if_crlf(std::string& s) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
replace_all(s, "\r\n", "\n");
|
||||||
|
#else
|
||||||
|
(void)(s);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
struct X {
|
||||||
|
constexpr static auto delim = ",";
|
||||||
|
constexpr static auto empty = "_EMPTY_";
|
||||||
|
int i;
|
||||||
|
double d;
|
||||||
|
std::string s;
|
||||||
|
|
||||||
|
[[nodiscard]] std::string to_string() const {
|
||||||
|
if (s == empty) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::to_string(i)
|
||||||
|
.append(delim)
|
||||||
|
.append(std::to_string(d))
|
||||||
|
.append(delim)
|
||||||
|
.append(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto tied() const {
|
||||||
|
return std::tie(i, d, s);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
[[nodiscard]] std::enable_if_t<ss::has_m_tied_t<T>, bool> operator==(
|
||||||
|
const T& lhs, const T& rhs) {
|
||||||
|
return lhs.tied() == rhs.tied();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
static void make_and_write(const std::string& file_name,
|
||||||
|
const std::vector<T>& data,
|
||||||
|
const std::vector<std::string>& header = {},
|
||||||
|
bool new_line_eof = true) {
|
||||||
|
std::ofstream out{file_name};
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
std::vector<const char*> new_lines = {"\n"};
|
||||||
|
#else
|
||||||
|
std::vector<const char*> new_lines = {"\n", "\r\n"};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
for (const auto& i : header) {
|
||||||
|
if (&i != &header.front()) {
|
||||||
|
out << T::delim;
|
||||||
|
}
|
||||||
|
out << i;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!header.empty()) {
|
||||||
|
out << new_lines.front();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < data.size(); ++i) {
|
||||||
|
out << data[i].to_string();
|
||||||
|
if (new_line_eof || i + 1 < data.size()) {
|
||||||
|
out << new_lines[i % new_lines.size()];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} /* anonymous namespace */
|
675
test/test_parser1_1.cpp
Normal file
675
test/test_parser1_1.cpp
Normal file
@ -0,0 +1,675 @@
|
|||||||
|
#include "test_parser1.hpp"
|
||||||
|
|
||||||
|
TEST_CASE("test file not found") {
|
||||||
|
unique_file_name f{"file_not_found"};
|
||||||
|
|
||||||
|
{
|
||||||
|
ss::parser p{f.name, ","};
|
||||||
|
CHECK_FALSE(p.valid());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
ss::parser<ss::string_error> p{f.name, ","};
|
||||||
|
CHECK_FALSE(p.valid());
|
||||||
|
CHECK_FALSE(p.error_msg().empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
ss::parser<ss::throw_on_error> p{f.name, ","};
|
||||||
|
FAIL("Expected exception...");
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
CHECK_FALSE(std::string{e.what()}.empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("test null buffer") {
|
||||||
|
{
|
||||||
|
ss::parser p{nullptr, 10, ","};
|
||||||
|
CHECK_FALSE(p.valid());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
ss::parser<ss::string_error> p{nullptr, 10, ","};
|
||||||
|
CHECK_FALSE(p.valid());
|
||||||
|
CHECK_FALSE(p.error_msg().empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
ss::parser<ss::throw_on_error> p{nullptr, 10, ","};
|
||||||
|
FAIL("Expected exception...");
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
CHECK_FALSE(std::string{e.what()}.empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Y {
|
||||||
|
constexpr static auto delim = ",";
|
||||||
|
std::string s1;
|
||||||
|
std::string s2;
|
||||||
|
std::string s3;
|
||||||
|
|
||||||
|
std::string to_string() const {
|
||||||
|
return std::string{}
|
||||||
|
.append(s1)
|
||||||
|
.append(delim)
|
||||||
|
.append(s2)
|
||||||
|
.append(delim)
|
||||||
|
.append(s3);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto tied() const {
|
||||||
|
return std::tie(s1, s2, s3);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_CASE_TEMPLATE("test position method", T, ParserOptionCombinations) {
|
||||||
|
constexpr auto buffer_mode = T::BufferMode::value;
|
||||||
|
using ErrorMode = typename T::ErrorMode;
|
||||||
|
|
||||||
|
unique_file_name f{"position_method"};
|
||||||
|
std::vector<Y> data = {{"1", "21", "x"}, {"321", "4", "y"},
|
||||||
|
{"54", "6", "zz"}, {"7", "876", "uuuu"},
|
||||||
|
{"910", "10", "v"}, {"10", "321", "ww"}};
|
||||||
|
make_and_write(f.name, data);
|
||||||
|
|
||||||
|
auto [p, buff] = make_parser<buffer_mode, ErrorMode>(f.name);
|
||||||
|
auto data_at = [&buff = buff, &f = f](auto n) {
|
||||||
|
if (!buff.empty()) {
|
||||||
|
return buff[n];
|
||||||
|
} else {
|
||||||
|
auto file = std::fopen(f.name.c_str(), "r");
|
||||||
|
std::fseek(file, n, SEEK_SET);
|
||||||
|
return static_cast<char>(std::fgetc(file));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
while (!p.eof()) {
|
||||||
|
auto curr_char = p.position();
|
||||||
|
const auto& [s1, s2, s3] =
|
||||||
|
p.template get_next<std::string, std::string, std::string>();
|
||||||
|
|
||||||
|
auto s = s1 + "," + s2 + "," + s3;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < s1.size(); ++i) {
|
||||||
|
CHECK_EQ(data_at(curr_char + i), s[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto last_char = data_at(curr_char + s.size());
|
||||||
|
CHECK((last_char == '\n' || last_char == '\r'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_TEMPLATE("test line method", T, ParserOptionCombinations) {
|
||||||
|
constexpr auto buffer_mode = T::BufferMode::value;
|
||||||
|
using ErrorMode = typename T::ErrorMode;
|
||||||
|
|
||||||
|
unique_file_name f{"line_method"};
|
||||||
|
std::vector<Y> data = {{"1", "21", "x"}, {"321", "4", "y"},
|
||||||
|
{"54", "6", "zz"}, {"7", "876", "uuuu"},
|
||||||
|
{"910", "10", "v"}, {"10", "321", "ww"}};
|
||||||
|
make_and_write(f.name, data);
|
||||||
|
|
||||||
|
auto [p, buff] = make_parser<buffer_mode, ErrorMode>(f.name);
|
||||||
|
|
||||||
|
auto expected_line = 0;
|
||||||
|
CHECK_EQ(p.line(), expected_line);
|
||||||
|
|
||||||
|
while (!p.eof()) {
|
||||||
|
std::ignore =
|
||||||
|
p.template get_next<std::string, std::string, std::string>();
|
||||||
|
++expected_line;
|
||||||
|
CHECK_EQ(p.line(), expected_line);
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK_EQ(p.line(), data.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_TEMPLATE("parser test various valid cases", T,
|
||||||
|
ParserOptionCombinations) {
|
||||||
|
constexpr auto buffer_mode = T::BufferMode::value;
|
||||||
|
using ErrorMode = typename T::ErrorMode;
|
||||||
|
|
||||||
|
unique_file_name f{"various_valid_cases"};
|
||||||
|
std::vector<X> data = {{1, 2, "x"}, {3, 4, "y"}, {5, 6, "z"},
|
||||||
|
{7, 8, "u"}, {9, 10, "v"}, {11, 12, "w"}};
|
||||||
|
make_and_write(f.name, data);
|
||||||
|
auto csv_data_buffer = make_buffer(f.name);
|
||||||
|
{
|
||||||
|
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
|
||||||
|
ss::parser p0{std::move(p)};
|
||||||
|
p = std::move(p0);
|
||||||
|
std::vector<X> i;
|
||||||
|
|
||||||
|
auto [p2, __] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
|
||||||
|
std::vector<X> i2;
|
||||||
|
|
||||||
|
auto move_rotate = [&p = p, &p0 = p0] {
|
||||||
|
auto p1 = std::move(p);
|
||||||
|
p0 = std::move(p1);
|
||||||
|
p = std::move(p0);
|
||||||
|
};
|
||||||
|
|
||||||
|
while (!p.eof()) {
|
||||||
|
move_rotate();
|
||||||
|
auto a = p.template get_next<int, double, std::string>();
|
||||||
|
i.emplace_back(ss::to_object<X>(a));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& a : p2.template iterate<int, double, std::string>()) {
|
||||||
|
i2.emplace_back(ss::to_object<X>(a));
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK_EQ(i, data);
|
||||||
|
CHECK_EQ(i2, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
|
||||||
|
std::vector<X> i;
|
||||||
|
|
||||||
|
auto [p2, __] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
|
||||||
|
std::vector<X> i2;
|
||||||
|
|
||||||
|
auto [p3, ___] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
|
||||||
|
std::vector<X> i3;
|
||||||
|
|
||||||
|
std::vector<X> expected = {std::begin(data) + 1, std::end(data)};
|
||||||
|
using tup = std::tuple<int, double, std::string>;
|
||||||
|
|
||||||
|
p.ignore_next();
|
||||||
|
while (!p.eof()) {
|
||||||
|
auto a = p.template get_next<tup>();
|
||||||
|
i.emplace_back(ss::to_object<X>(a));
|
||||||
|
}
|
||||||
|
|
||||||
|
p2.ignore_next();
|
||||||
|
for (const auto& a : p2.template iterate<tup>()) {
|
||||||
|
i2.emplace_back(ss::to_object<X>(a));
|
||||||
|
}
|
||||||
|
|
||||||
|
p3.ignore_next();
|
||||||
|
for (auto it = p3.template iterate<tup>().begin();
|
||||||
|
it != p3.template iterate<tup>().end(); ++it) {
|
||||||
|
i3.emplace_back(ss::to_object<X>(*it));
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK_EQ(i, expected);
|
||||||
|
CHECK_EQ(i2, expected);
|
||||||
|
CHECK_EQ(i3, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
|
||||||
|
std::vector<X> i;
|
||||||
|
auto [p2, __] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
|
||||||
|
std::vector<X> i2;
|
||||||
|
|
||||||
|
while (!p.eof()) {
|
||||||
|
i.push_back(p.template get_object<X, int, double, std::string>());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto&& a :
|
||||||
|
p2.template iterate_object<X, int, double, std::string>()) {
|
||||||
|
i2.push_back(std::move(a));
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK_EQ(i, data);
|
||||||
|
CHECK_EQ(i2, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
|
||||||
|
std::vector<X> i;
|
||||||
|
|
||||||
|
for (auto&& a :
|
||||||
|
p.template iterate_object<X, int, double, std::string>()) {
|
||||||
|
i.push_back(std::move(a));
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK_EQ(i, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
|
||||||
|
std::vector<X> i;
|
||||||
|
|
||||||
|
auto [p2, __] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
|
||||||
|
std::vector<X> i2;
|
||||||
|
|
||||||
|
using tup = std::tuple<int, double, std::string>;
|
||||||
|
while (!p.eof()) {
|
||||||
|
i.push_back(p.template get_object<X, tup>());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto it = p2.template iterate_object<X, tup>().begin();
|
||||||
|
it != p2.template iterate_object<X, tup>().end(); it++) {
|
||||||
|
i2.push_back({it->i, it->d, it->s});
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK_EQ(i, data);
|
||||||
|
CHECK_EQ(i2, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
|
||||||
|
std::vector<X> i;
|
||||||
|
|
||||||
|
using tup = std::tuple<int, double, std::string>;
|
||||||
|
for (auto&& a : p.template iterate_object<X, tup>()) {
|
||||||
|
i.push_back(std::move(a));
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK_EQ(i, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
|
||||||
|
std::vector<X> i;
|
||||||
|
|
||||||
|
while (!p.eof()) {
|
||||||
|
i.push_back(p.template get_next<X>());
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK_EQ(i, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
|
||||||
|
std::vector<X> i;
|
||||||
|
|
||||||
|
for (auto&& a : p.template iterate<X>()) {
|
||||||
|
i.push_back(std::move(a));
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK_EQ(i, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
constexpr int excluded = 3;
|
||||||
|
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
|
||||||
|
std::vector<X> i;
|
||||||
|
|
||||||
|
auto [p2, __] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
|
||||||
|
std::vector<X> i2;
|
||||||
|
|
||||||
|
while (!p.eof()) {
|
||||||
|
try {
|
||||||
|
auto a = p.template get_object<X, ss::ax<int, excluded>, double,
|
||||||
|
std::string>();
|
||||||
|
if (p.valid()) {
|
||||||
|
i.push_back(a);
|
||||||
|
}
|
||||||
|
} catch (...) {
|
||||||
|
// ignore
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!T::ThrowOnError) {
|
||||||
|
for (auto&& a : p2.template iterate_object<X, ss::ax<int, excluded>,
|
||||||
|
double, std::string>()) {
|
||||||
|
if (p2.valid()) {
|
||||||
|
i2.push_back(std::move(a));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<X> expected;
|
||||||
|
for (auto& x : data) {
|
||||||
|
if (x.i != excluded) {
|
||||||
|
expected.push_back(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::copy_if(data.begin(), data.end(), expected.begin(),
|
||||||
|
[&](const X& x) { return x.i != excluded; });
|
||||||
|
CHECK_EQ(i, expected);
|
||||||
|
|
||||||
|
if (!T::ThrowOnError) {
|
||||||
|
CHECK_EQ(i2, expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
|
||||||
|
std::vector<X> i;
|
||||||
|
|
||||||
|
auto [p2, __] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
|
||||||
|
std::vector<X> i2;
|
||||||
|
|
||||||
|
while (!p.eof()) {
|
||||||
|
try {
|
||||||
|
auto a = p.template get_object<X, ss::nx<int, 3>, double,
|
||||||
|
std::string>();
|
||||||
|
if (p.valid()) {
|
||||||
|
i.push_back(a);
|
||||||
|
}
|
||||||
|
} catch (...) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!T::ThrowOnError) {
|
||||||
|
for (auto&& a : p2.template iterate_object<X, ss::nx<int, 3>,
|
||||||
|
double, std::string>()) {
|
||||||
|
if (p2.valid()) {
|
||||||
|
i2.push_back(std::move(a));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<X> expected = {{3, 4, "y"}};
|
||||||
|
CHECK_EQ(i, expected);
|
||||||
|
if (!T::ThrowOnError) {
|
||||||
|
CHECK_EQ(i2, expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
unique_file_name empty_f{"various_valid_cases"};
|
||||||
|
std::vector<X> empty_data = {};
|
||||||
|
|
||||||
|
make_and_write(empty_f.name, empty_data);
|
||||||
|
|
||||||
|
auto [p, _] = make_parser<buffer_mode, ErrorMode>(empty_f.name, ",");
|
||||||
|
std::vector<X> i;
|
||||||
|
|
||||||
|
auto [p2, __] = make_parser<buffer_mode, ErrorMode>(empty_f.name, ",");
|
||||||
|
std::vector<X> i2;
|
||||||
|
|
||||||
|
while (!p.eof()) {
|
||||||
|
i.push_back(p.template get_next<X>());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto&& a : p2.template iterate<X>()) {
|
||||||
|
i2.push_back(std::move(a));
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK(i.empty());
|
||||||
|
CHECK(i2.empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
using test_tuple = std::tuple<double, char, double>;
|
||||||
|
struct test_struct {
|
||||||
|
int i;
|
||||||
|
double d;
|
||||||
|
char c;
|
||||||
|
auto tied() {
|
||||||
|
return std::tie(i, d, c);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline void expect_test_struct(const test_struct&) {
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_TEMPLATE("parser test composite conversion", BufferMode,
|
||||||
|
std::true_type, std::false_type) {
|
||||||
|
constexpr auto buffer_mode = BufferMode::value;
|
||||||
|
unique_file_name f{"composite_conversion"};
|
||||||
|
{
|
||||||
|
std::ofstream out{f.name};
|
||||||
|
for (auto& i :
|
||||||
|
{"10,a,11.1", "10,20,11.1", "junk", "10,11.1", "1,11.1,a", "junk",
|
||||||
|
"10,junk", "11,junk", "10,11.1,c", "10,20", "10,22.2,f"}) {
|
||||||
|
out << i << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto [p, _] = make_parser<buffer_mode, ss::string_error>(f.name, ",");
|
||||||
|
auto fail = [] { FAIL(""); };
|
||||||
|
auto expect_error = [](auto error) { CHECK(!error.empty()); };
|
||||||
|
auto ignore_error = [] {};
|
||||||
|
|
||||||
|
REQUIRE(p.valid());
|
||||||
|
REQUIRE_FALSE(p.eof());
|
||||||
|
|
||||||
|
{
|
||||||
|
constexpr static auto expectedData = std::tuple{10, 'a', 11.1};
|
||||||
|
|
||||||
|
auto [d1, d2, d3, d4] =
|
||||||
|
p.template try_next<int, int, double>(fail)
|
||||||
|
.template or_else<test_struct>(fail)
|
||||||
|
.template or_else<int, char, double>(
|
||||||
|
[&](auto&& data) { CHECK_EQ(data, expectedData); })
|
||||||
|
.on_error(fail)
|
||||||
|
.template or_else<test_tuple>(fail)
|
||||||
|
.values();
|
||||||
|
|
||||||
|
REQUIRE(p.valid());
|
||||||
|
REQUIRE_FALSE(d1);
|
||||||
|
REQUIRE_FALSE(d2);
|
||||||
|
REQUIRE(d3);
|
||||||
|
REQUIRE_FALSE(d4);
|
||||||
|
CHECK_EQ(*d3, expectedData);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
REQUIRE(!p.eof());
|
||||||
|
constexpr static auto expectedData = std::tuple{10, 20, 11.1};
|
||||||
|
|
||||||
|
auto [d1, d2, d3, d4] =
|
||||||
|
p.template try_next<int, int, double>(
|
||||||
|
[&](auto& i1, auto i2, double d) {
|
||||||
|
CHECK_EQ(std::tie(i1, i2, d), expectedData);
|
||||||
|
})
|
||||||
|
.on_error(fail)
|
||||||
|
.template or_object<test_struct, int, double, char>(fail)
|
||||||
|
.on_error(fail)
|
||||||
|
.template or_else<test_tuple>(fail)
|
||||||
|
.on_error(fail)
|
||||||
|
.template or_else<int, char, double>(fail)
|
||||||
|
.values();
|
||||||
|
|
||||||
|
REQUIRE(p.valid());
|
||||||
|
REQUIRE(d1);
|
||||||
|
REQUIRE_FALSE(d2);
|
||||||
|
REQUIRE_FALSE(d3);
|
||||||
|
REQUIRE_FALSE(d4);
|
||||||
|
CHECK_EQ(*d1, expectedData);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
REQUIRE(!p.eof());
|
||||||
|
|
||||||
|
auto [d1, d2, d3, d4, d5] =
|
||||||
|
p.template try_object<test_struct, int, double, char>(fail)
|
||||||
|
.on_error(expect_error)
|
||||||
|
.template or_else<int, char, char>(fail)
|
||||||
|
.template or_else<test_struct>(fail)
|
||||||
|
.template or_else<test_tuple>(fail)
|
||||||
|
.template or_else<int, char, double>(fail)
|
||||||
|
.values();
|
||||||
|
|
||||||
|
REQUIRE_FALSE(p.valid());
|
||||||
|
REQUIRE_FALSE(d1);
|
||||||
|
REQUIRE_FALSE(d2);
|
||||||
|
REQUIRE_FALSE(d3);
|
||||||
|
REQUIRE_FALSE(d4);
|
||||||
|
REQUIRE_FALSE(d5);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
REQUIRE(!p.eof());
|
||||||
|
|
||||||
|
auto [d1, d2] =
|
||||||
|
p.template try_next<int, double>([](auto& i, auto& d) {
|
||||||
|
REQUIRE_EQ(std::tie(i, d), std::tuple{10, 11.1});
|
||||||
|
})
|
||||||
|
.template or_else<int, double>([](auto&, auto&) { FAIL(""); })
|
||||||
|
.values();
|
||||||
|
|
||||||
|
REQUIRE(p.valid());
|
||||||
|
REQUIRE(d1);
|
||||||
|
REQUIRE_FALSE(d2);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
REQUIRE(!p.eof());
|
||||||
|
|
||||||
|
auto [d1, d2] =
|
||||||
|
p.template try_next<int, double>([](auto&, auto&) { FAIL(""); })
|
||||||
|
.template or_else<test_struct>(expect_test_struct)
|
||||||
|
.values();
|
||||||
|
|
||||||
|
REQUIRE(p.valid());
|
||||||
|
REQUIRE_FALSE(d1);
|
||||||
|
REQUIRE(d2);
|
||||||
|
CHECK_EQ(d2->tied(), std::tuple{1, 11.1, 'a'});
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
REQUIRE(!p.eof());
|
||||||
|
|
||||||
|
auto [d1, d2, d3, d4, d5] =
|
||||||
|
p.template try_next<int, int, double>(fail)
|
||||||
|
.template or_object<test_struct, int, double, char>()
|
||||||
|
.template or_else<test_struct>(expect_test_struct)
|
||||||
|
.template or_else<test_tuple>(fail)
|
||||||
|
.template or_else<std::tuple<int, double>>(fail)
|
||||||
|
.on_error(ignore_error)
|
||||||
|
.on_error(expect_error)
|
||||||
|
.values();
|
||||||
|
|
||||||
|
REQUIRE_FALSE(p.valid());
|
||||||
|
REQUIRE_FALSE(d1);
|
||||||
|
REQUIRE_FALSE(d2);
|
||||||
|
REQUIRE_FALSE(d3);
|
||||||
|
REQUIRE_FALSE(d4);
|
||||||
|
REQUIRE_FALSE(d5);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
REQUIRE(!p.eof());
|
||||||
|
|
||||||
|
auto [d1, d2] =
|
||||||
|
p.template try_next<int, std::optional<int>>()
|
||||||
|
.on_error(ignore_error)
|
||||||
|
.on_error(fail)
|
||||||
|
.template or_else<std::tuple<int, std::string>>(fail)
|
||||||
|
.on_error(ignore_error)
|
||||||
|
.on_error(fail)
|
||||||
|
.on_error(ignore_error)
|
||||||
|
.values();
|
||||||
|
|
||||||
|
REQUIRE(p.valid());
|
||||||
|
REQUIRE(d1);
|
||||||
|
REQUIRE_FALSE(d2);
|
||||||
|
CHECK_EQ(*d1, std::tuple{10, std::nullopt});
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
REQUIRE_FALSE(p.eof());
|
||||||
|
|
||||||
|
auto [d1, d2] =
|
||||||
|
p.template try_next<int, std::variant<int, std::string>>()
|
||||||
|
.on_error(fail)
|
||||||
|
.template or_else<std::tuple<int, std::string>>(fail)
|
||||||
|
.on_error(fail)
|
||||||
|
.values();
|
||||||
|
|
||||||
|
REQUIRE(p.valid());
|
||||||
|
REQUIRE(d1);
|
||||||
|
REQUIRE_FALSE(d2);
|
||||||
|
CHECK_EQ(*d1, std::tuple{11, std::variant<int, std::string>{"junk"}});
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
REQUIRE(!p.eof());
|
||||||
|
|
||||||
|
auto [d1, d2] = p.template try_object<test_struct, int, double, char>()
|
||||||
|
.template or_else<int>(fail)
|
||||||
|
.values();
|
||||||
|
REQUIRE(p.valid());
|
||||||
|
REQUIRE(d1);
|
||||||
|
REQUIRE_FALSE(d2);
|
||||||
|
CHECK_EQ(d1->tied(), std::tuple{10, 11.1, 'c'});
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
REQUIRE_FALSE(p.eof());
|
||||||
|
|
||||||
|
auto [d1, d2, d3, d4] =
|
||||||
|
p.template try_next<int, int>([] { return false; })
|
||||||
|
.template or_else<int, double>([](auto&) { return false; })
|
||||||
|
.template or_else<int, int>()
|
||||||
|
.template or_else<int, int>(fail)
|
||||||
|
.values();
|
||||||
|
|
||||||
|
REQUIRE(p.valid());
|
||||||
|
REQUIRE_FALSE(d1);
|
||||||
|
REQUIRE_FALSE(d2);
|
||||||
|
REQUIRE(d3);
|
||||||
|
REQUIRE_FALSE(d4);
|
||||||
|
CHECK_EQ(d3.value(), std::tuple{10, 20});
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
REQUIRE(!p.eof());
|
||||||
|
|
||||||
|
auto [d1, d2, d3, d4] =
|
||||||
|
p.template try_object<test_struct, int, double, char>(
|
||||||
|
[] { return false; })
|
||||||
|
.template or_else<int, double>([](auto&) { return false; })
|
||||||
|
.template or_object<test_struct, int, double, char>()
|
||||||
|
.template or_else<int, int>(fail)
|
||||||
|
.values();
|
||||||
|
|
||||||
|
REQUIRE(p.valid());
|
||||||
|
REQUIRE_FALSE(d1);
|
||||||
|
REQUIRE_FALSE(d2);
|
||||||
|
REQUIRE(d3);
|
||||||
|
REQUIRE_FALSE(d4);
|
||||||
|
CHECK_EQ(d3->tied(), std::tuple{10, 22.2, 'f'});
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK(p.eof());
|
||||||
|
}
|
||||||
|
|
||||||
|
template <bool buffer_mode, typename... Ts>
|
||||||
|
void test_no_new_line_at_eof_impl(const std::vector<X>& data) {
|
||||||
|
unique_file_name f{"no_new_line_at_eof"};
|
||||||
|
make_and_write(f.name, data, {}, false);
|
||||||
|
|
||||||
|
auto [p, _] = make_parser<buffer_mode, Ts...>(f.name);
|
||||||
|
std::vector<X> parsed_data;
|
||||||
|
|
||||||
|
for (const auto& el : p.template iterate<X>()) {
|
||||||
|
parsed_data.push_back(el);
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK_EQ(data, parsed_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <bool buffer_mode, typename... Ts>
|
||||||
|
void test_no_new_line_at_eof() {
|
||||||
|
test_no_new_line_at_eof_impl<buffer_mode, Ts...>({});
|
||||||
|
test_no_new_line_at_eof_impl<buffer_mode, Ts...>({{1, 2, "X"}});
|
||||||
|
test_no_new_line_at_eof_impl<buffer_mode, Ts...>({{1, 2, "X"}, {}});
|
||||||
|
test_no_new_line_at_eof_impl<buffer_mode, Ts...>(
|
||||||
|
{{1, 2, "X"}, {3, 4, "YY"}});
|
||||||
|
test_no_new_line_at_eof_impl<buffer_mode, Ts...>(
|
||||||
|
{{1, 2, "X"}, {3, 4, "YY"}, {}});
|
||||||
|
test_no_new_line_at_eof_impl<buffer_mode, Ts...>(
|
||||||
|
{{1, 2, "X"}, {3, 4, "YY"}, {5, 6, "ZZZ"}, {7, 8, "UUU"}});
|
||||||
|
|
||||||
|
for (size_t i = 0; i < 2 * ss::get_line_initial_buffer_size; ++i) {
|
||||||
|
test_no_new_line_at_eof_impl<buffer_mode, Ts...>(
|
||||||
|
{{1, 2, std::string(i, 'X')}});
|
||||||
|
|
||||||
|
for (size_t j = 0; j < 2 * ss::get_line_initial_buffer_size; j += 13) {
|
||||||
|
|
||||||
|
test_no_new_line_at_eof_impl<buffer_mode, Ts...>(
|
||||||
|
{{1, 2, std::string(i, 'X')}, {3, 4, std::string(j, 'Y')}});
|
||||||
|
|
||||||
|
test_no_new_line_at_eof_impl<buffer_mode, Ts...>(
|
||||||
|
{{1, 2, std::string(j, 'X')}, {3, 4, std::string(i, 'Y')}});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_TEMPLATE("test no new line at end of data", T,
|
||||||
|
ParserOptionCombinations) {
|
||||||
|
constexpr auto buffer_mode = T::BufferMode::value;
|
||||||
|
using ErrorMode = typename T::ErrorMode;
|
||||||
|
test_no_new_line_at_eof<buffer_mode, ErrorMode>();
|
||||||
|
}
|
265
test/test_parser1_2.cpp
Normal file
265
test/test_parser1_2.cpp
Normal file
@ -0,0 +1,265 @@
|
|||||||
|
#include "test_parser1.hpp"
|
||||||
|
|
||||||
|
struct my_string {
|
||||||
|
char* data{nullptr};
|
||||||
|
|
||||||
|
my_string() = default;
|
||||||
|
|
||||||
|
~my_string() {
|
||||||
|
delete[] data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure no object is copied
|
||||||
|
my_string(const my_string&) = delete;
|
||||||
|
my_string& operator=(const my_string&) = delete;
|
||||||
|
|
||||||
|
my_string(my_string&& other) : data{other.data} {
|
||||||
|
other.data = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
my_string& operator=(my_string&& other) {
|
||||||
|
data = other.data;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline bool ss::extract(const char* begin, const char* end, my_string& s) {
|
||||||
|
size_t size = end - begin;
|
||||||
|
s.data = new char[size + 1];
|
||||||
|
strncpy(s.data, begin, size);
|
||||||
|
s.data[size] = '\0';
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct xyz {
|
||||||
|
my_string x;
|
||||||
|
my_string y;
|
||||||
|
my_string z;
|
||||||
|
auto tied() {
|
||||||
|
return std::tie(x, y, z);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_CASE_TEMPLATE("test moving of parsed composite values", T,
|
||||||
|
config<std::true_type>, config<std::false_type>,
|
||||||
|
config<std::true_type, ss::string_error>,
|
||||||
|
config<std::false_type, ss::string_error>) {
|
||||||
|
constexpr auto buffer_mode = T::BufferMode::value;
|
||||||
|
using ErrorMode = typename T::ErrorMode;
|
||||||
|
|
||||||
|
// to compile is enough
|
||||||
|
return;
|
||||||
|
auto [p, _] = make_parser<buffer_mode, ErrorMode>("", "");
|
||||||
|
std::ignore =
|
||||||
|
p.template try_next<my_string, my_string, my_string>()
|
||||||
|
.template or_else<my_string, my_string, my_string, my_string>(
|
||||||
|
[](auto&&) {})
|
||||||
|
.template or_else<my_string>([](auto&) {})
|
||||||
|
.template or_else<xyz>([](auto&&) {})
|
||||||
|
.template or_object<xyz, my_string, my_string, my_string>(
|
||||||
|
[](auto&&) {})
|
||||||
|
.template or_else<std::tuple<my_string, my_string, my_string>>(
|
||||||
|
[](auto&, auto&, auto&) {});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_TEMPLATE("parser test string error mode", BufferMode, std::true_type,
|
||||||
|
std::false_type) {
|
||||||
|
unique_file_name f{"string_error"};
|
||||||
|
{
|
||||||
|
std::ofstream out{f.name};
|
||||||
|
out << "junk" << std::endl;
|
||||||
|
out << "junk" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto [p, _] = make_parser<BufferMode::value, ss::string_error>(f.name, ",");
|
||||||
|
|
||||||
|
REQUIRE_FALSE(p.eof());
|
||||||
|
std::ignore = p.template get_next<int>();
|
||||||
|
CHECK_FALSE(p.valid());
|
||||||
|
CHECK_FALSE(p.error_msg().empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_TEMPLATE("parser throw on error mode", BufferMode, std::true_type,
|
||||||
|
std::false_type) {
|
||||||
|
unique_file_name f{"throw_on_error"};
|
||||||
|
{
|
||||||
|
std::ofstream out{f.name};
|
||||||
|
out << "junk" << std::endl;
|
||||||
|
out << "junk" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto [p, _] =
|
||||||
|
make_parser<BufferMode::value, ss::throw_on_error>(f.name, ",");
|
||||||
|
|
||||||
|
REQUIRE_FALSE(p.eof());
|
||||||
|
try {
|
||||||
|
std::ignore = p.template get_next<int>();
|
||||||
|
FAIL("Expected exception...");
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
CHECK_FALSE(std::string{e.what()}.empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline std::string no_quote(const std::string& s) {
|
||||||
|
if (!s.empty() && s[0] == '"') {
|
||||||
|
return {std::next(begin(s)), std::prev(end(s))};
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_TEMPLATE("test quote multiline", T, ParserOptionCombinations) {
|
||||||
|
constexpr auto buffer_mode = T::BufferMode::value;
|
||||||
|
using ErrorMode = typename T::ErrorMode;
|
||||||
|
|
||||||
|
unique_file_name f{"quote_multiline"};
|
||||||
|
std::vector<X> data = {{1, 2, "\"x\r\nx\nx\""},
|
||||||
|
{3, 4, "\"y\ny\r\ny\""},
|
||||||
|
{5, 6, "\"z\nz\""},
|
||||||
|
{7, 8, "\"u\"\"\""},
|
||||||
|
{9, 10, "v"},
|
||||||
|
{11, 12, "\"w\n\""}};
|
||||||
|
for (auto& [_, __, s] : data) {
|
||||||
|
update_if_crlf(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
make_and_write(f.name, data);
|
||||||
|
for (auto& [_, __, s] : data) {
|
||||||
|
s = no_quote(s);
|
||||||
|
if (s[0] == 'u') {
|
||||||
|
s = "u\"";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto [p, _] = make_parser<buffer_mode, ErrorMode, ss::multiline,
|
||||||
|
ss::quote<'"'>>(f.name, ",");
|
||||||
|
|
||||||
|
std::vector<X> i;
|
||||||
|
|
||||||
|
while (!p.eof()) {
|
||||||
|
auto a = p.template get_next<int, double, std::string>();
|
||||||
|
i.emplace_back(ss::to_object<X>(a));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& [_, __, s] : i) {
|
||||||
|
update_if_crlf(s);
|
||||||
|
}
|
||||||
|
CHECK_EQ(i, data);
|
||||||
|
|
||||||
|
auto [p_no_multiline, __] =
|
||||||
|
make_parser<buffer_mode, ErrorMode, ss::quote<'"'>>(f.name, ",");
|
||||||
|
while (!p.eof()) {
|
||||||
|
auto command = [&p_no_multiline = p_no_multiline] {
|
||||||
|
std::ignore =
|
||||||
|
p_no_multiline.template get_next<int, double, std::string>();
|
||||||
|
};
|
||||||
|
expect_error_on_command(p_no_multiline, command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline std::string no_escape(std::string& s) {
|
||||||
|
s.erase(std::remove(begin(s), end(s), '\\'), end(s));
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_TEMPLATE("test escape multiline", T, ParserOptionCombinations) {
|
||||||
|
constexpr auto buffer_mode = T::BufferMode::value;
|
||||||
|
using ErrorMode = typename T::ErrorMode;
|
||||||
|
|
||||||
|
unique_file_name f{"escape_multiline"};
|
||||||
|
std::vector<X> data = {{1, 2, "x\\\nx\\\r\nx"},
|
||||||
|
{5, 6, "z\\\nz\\\nz"},
|
||||||
|
{7, 8, "u"},
|
||||||
|
{3, 4, "y\\\ny\\\ny"},
|
||||||
|
{9, 10, "v\\\\"},
|
||||||
|
{11, 12, "w\\\n"}};
|
||||||
|
for (auto& [_, __, s] : data) {
|
||||||
|
update_if_crlf(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
make_and_write(f.name, data);
|
||||||
|
for (auto& [_, __, s] : data) {
|
||||||
|
s = no_escape(s);
|
||||||
|
if (s == "v") {
|
||||||
|
s = "v\\";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto [p, _] = make_parser<buffer_mode, ErrorMode, ss::multiline,
|
||||||
|
ss::escape<'\\'>>(f.name, ",");
|
||||||
|
std::vector<X> i;
|
||||||
|
|
||||||
|
while (!p.eof()) {
|
||||||
|
auto a = p.template get_next<int, double, std::string>();
|
||||||
|
i.emplace_back(ss::to_object<X>(a));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& [_, __, s] : i) {
|
||||||
|
update_if_crlf(s);
|
||||||
|
}
|
||||||
|
CHECK_EQ(i, data);
|
||||||
|
|
||||||
|
auto [p_no_multiline, __] =
|
||||||
|
make_parser<buffer_mode, ErrorMode, ss::escape<'\\'>>(f.name, ",");
|
||||||
|
while (!p.eof()) {
|
||||||
|
auto command = [&p_no_multiline = p_no_multiline] {
|
||||||
|
auto a =
|
||||||
|
p_no_multiline.template get_next<int, double, std::string>();
|
||||||
|
};
|
||||||
|
expect_error_on_command(p_no_multiline, command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_TEMPLATE("test quote escape multiline", T, ParserOptionCombinations) {
|
||||||
|
constexpr auto buffer_mode = T::BufferMode::value;
|
||||||
|
using ErrorMode = typename T::ErrorMode;
|
||||||
|
|
||||||
|
unique_file_name f{"quote_escape_multiline"};
|
||||||
|
{
|
||||||
|
std::ofstream out{f.name};
|
||||||
|
out << "1,2,\"just\\\n\nstrings\"" << std::endl;
|
||||||
|
#ifndef _WIN32
|
||||||
|
out << "3,4,\"just\r\nsome\\\r\n\n\\\nstrings\"" << std::endl;
|
||||||
|
out << "5,6,\"just\\\n\\\r\n\r\n\nstrings" << std::endl;
|
||||||
|
#else
|
||||||
|
out << "3,4,\"just\nsome\\\n\n\\\nstrings\"" << std::endl;
|
||||||
|
out << "5,6,\"just\\\n\\\n\n\nstrings" << std::endl;
|
||||||
|
#endif
|
||||||
|
out << "7,8,\"just strings\"" << std::endl;
|
||||||
|
out << "9,10,just strings" << std::endl;
|
||||||
|
}
|
||||||
|
size_t bad_lines = 1;
|
||||||
|
auto num_errors = 0;
|
||||||
|
|
||||||
|
auto [p, _] = make_parser<buffer_mode, ErrorMode, ss::multiline,
|
||||||
|
ss::escape<'\\'>, ss::quote<'"'>>(f.name);
|
||||||
|
std::vector<X> i;
|
||||||
|
|
||||||
|
while (!p.eof()) {
|
||||||
|
try {
|
||||||
|
auto a = p.template get_next<int, double, std::string>();
|
||||||
|
if (p.valid()) {
|
||||||
|
i.emplace_back(ss::to_object<X>(a));
|
||||||
|
} else {
|
||||||
|
++num_errors;
|
||||||
|
}
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
++num_errors;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK(bad_lines == num_errors);
|
||||||
|
|
||||||
|
std::vector<X> data = {{1, 2, "just\n\nstrings"},
|
||||||
|
#ifndef _WIN32
|
||||||
|
{3, 4, "just\r\nsome\r\n\n\nstrings"},
|
||||||
|
#else
|
||||||
|
{3, 4, "just\nsome\n\n\nstrings"},
|
||||||
|
#endif
|
||||||
|
{9, 10, "just strings"}};
|
||||||
|
|
||||||
|
for (auto& [_, __, s] : i) {
|
||||||
|
update_if_crlf(s);
|
||||||
|
}
|
||||||
|
CHECK_EQ(i, data);
|
||||||
|
}
|
314
test/test_parser1_3.cpp
Normal file
314
test/test_parser1_3.cpp
Normal file
@ -0,0 +1,314 @@
|
|||||||
|
#include "test_parser1.hpp"
|
||||||
|
|
||||||
|
TEST_CASE_TEMPLATE("test multiline restricted", T, ParserOptionCombinations) {
|
||||||
|
constexpr auto buffer_mode = T::BufferMode::value;
|
||||||
|
using ErrorMode = typename T::ErrorMode;
|
||||||
|
|
||||||
|
unique_file_name f{"multiline_restricted"};
|
||||||
|
{
|
||||||
|
std::ofstream out{f.name};
|
||||||
|
out << "1,2,\"just\n\nstrings\"" << std::endl;
|
||||||
|
#ifndef _WIN32
|
||||||
|
out << "3,4,\"ju\n\r\n\nnk\"" << std::endl;
|
||||||
|
out << "5,6,just\\\n\\\r\nstrings" << std::endl;
|
||||||
|
#else
|
||||||
|
out << "3,4,\"ju\n\n\nnk\"" << std::endl;
|
||||||
|
out << "5,6,just\\\n\\\nstrings" << std::endl;
|
||||||
|
#endif
|
||||||
|
out << "7,8,ju\\\n\\\n\\\nnk" << std::endl;
|
||||||
|
out << "99,100,\"\n\n\n\n" << std::endl;
|
||||||
|
out << "9,10,\"just\\\n\nstrings\"" << std::endl;
|
||||||
|
out << "11,12,\"ju\\\n|\n\n\n\n\nk\"" << std::endl;
|
||||||
|
out << "13,14,\"ju\\\n\\\n15,16\"\\\n\\\\\n\nnk\"" << std::endl;
|
||||||
|
out << "17,18,\"ju\\\n\\\n\\\n\\\\\n\nnk\"" << std::endl;
|
||||||
|
out << "19,20,just strings" << std::endl;
|
||||||
|
}
|
||||||
|
auto bad_lines = 20;
|
||||||
|
auto num_errors = 0;
|
||||||
|
|
||||||
|
auto [p, _] =
|
||||||
|
make_parser<buffer_mode, ErrorMode, ss::multiline_restricted<2>,
|
||||||
|
ss::quote<'"'>, ss::escape<'\\'>>(f.name, ",");
|
||||||
|
std::vector<X> i;
|
||||||
|
|
||||||
|
while (!p.eof()) {
|
||||||
|
try {
|
||||||
|
auto a = p.template get_next<int, double, std::string>();
|
||||||
|
if (p.valid()) {
|
||||||
|
i.emplace_back(ss::to_object<X>(a));
|
||||||
|
} else {
|
||||||
|
++num_errors;
|
||||||
|
}
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
++num_errors;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK(bad_lines == num_errors);
|
||||||
|
|
||||||
|
std::vector<X> data = {{1, 2, "just\n\nstrings"},
|
||||||
|
#ifndef _WIN32
|
||||||
|
{5, 6, "just\n\r\nstrings"},
|
||||||
|
#else
|
||||||
|
{5, 6, "just\n\nstrings"},
|
||||||
|
#endif
|
||||||
|
{9, 10, "just\n\nstrings"},
|
||||||
|
{19, 20, "just strings"}};
|
||||||
|
|
||||||
|
for (auto& [_, __, s] : i) {
|
||||||
|
update_if_crlf(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i.size() != data.size()) {
|
||||||
|
CHECK_EQ(i.size(), data.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK_EQ(i, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename... Ts>
|
||||||
|
void test_unterminated_line(const std::vector<std::string>& lines,
|
||||||
|
size_t bad_line) {
|
||||||
|
constexpr auto buffer_mode = T::BufferMode::value;
|
||||||
|
using ErrorMode = typename T::ErrorMode;
|
||||||
|
|
||||||
|
unique_file_name f{"unterminated_line"};
|
||||||
|
std::ofstream out{f.name};
|
||||||
|
for (const auto& line : lines) {
|
||||||
|
out << line << std::endl;
|
||||||
|
}
|
||||||
|
out.close();
|
||||||
|
|
||||||
|
auto [p, _] = make_parser<buffer_mode, ErrorMode, Ts...>(f.name);
|
||||||
|
size_t line = 0;
|
||||||
|
while (!p.eof()) {
|
||||||
|
auto command = [&p = p] {
|
||||||
|
std::ignore = p.template get_next<int, double, std::string>();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (line == bad_line) {
|
||||||
|
expect_error_on_command(p, command);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
command();
|
||||||
|
CHECK(p.valid());
|
||||||
|
++line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_TEMPLATE("parser test csv on multiline with errors", T,
|
||||||
|
ParserOptionCombinations) {
|
||||||
|
using multiline = ss::multiline_restricted<3>;
|
||||||
|
using escape = ss::escape<'\\'>;
|
||||||
|
using quote = ss::quote<'"'>;
|
||||||
|
|
||||||
|
// unterminated escape
|
||||||
|
{
|
||||||
|
const std::vector<std::string> lines{"1,2,just\\"};
|
||||||
|
test_unterminated_line<T, multiline, escape>(lines, 0);
|
||||||
|
test_unterminated_line<T, multiline, escape, quote>(lines, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const std::vector<std::string> lines{"1,2,just\\", "9,8,second"};
|
||||||
|
test_unterminated_line<T, multiline, escape>(lines, 0);
|
||||||
|
test_unterminated_line<T, multiline, escape, quote>(lines, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const std::vector<std::string> lines{"9,8,first", "1,2,just\\"};
|
||||||
|
test_unterminated_line<T, multiline, escape>(lines, 1);
|
||||||
|
test_unterminated_line<T, multiline, escape, quote>(lines, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const std::vector<std::string> lines{"9,8,first", "1,2,just\\",
|
||||||
|
"3,4,third"};
|
||||||
|
test_unterminated_line<T, multiline, escape>(lines, 1);
|
||||||
|
test_unterminated_line<T, multiline, escape, quote>(lines, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const std::vector<std::string> lines{"9,8,first",
|
||||||
|
"1,2,just\\\nstrings\\",
|
||||||
|
"3,4,th\\\nird"};
|
||||||
|
test_unterminated_line<T, multiline, escape>(lines, 1);
|
||||||
|
test_unterminated_line<T, multiline, escape, quote>(lines, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const std::vector<std::string> lines{"9,8,first", "3,4,second",
|
||||||
|
"1,2,just\\"};
|
||||||
|
test_unterminated_line<T, multiline, escape>(lines, 2);
|
||||||
|
test_unterminated_line<T, multiline, escape, quote>(lines, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const std::vector<std::string> lines{"9,8,\\first", "3,4,second",
|
||||||
|
"1,2,jus\\t\\"};
|
||||||
|
test_unterminated_line<T, multiline, escape>(lines, 2);
|
||||||
|
test_unterminated_line<T, multiline, escape, quote>(lines, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// unterminated quote
|
||||||
|
{
|
||||||
|
const std::vector<std::string> lines{"1,2,\"just"};
|
||||||
|
test_unterminated_line<T, multiline, escape, quote>(lines, 0);
|
||||||
|
test_unterminated_line<T, multiline, quote>(lines, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const std::vector<std::string> lines{"1,2,\"just", "9,8,second"};
|
||||||
|
test_unterminated_line<T, multiline, escape, quote>(lines, 0);
|
||||||
|
test_unterminated_line<T, multiline, quote>(lines, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const std::vector<std::string> lines{"9,8,first", "1,2,\"just"};
|
||||||
|
test_unterminated_line<T, multiline, escape, quote>(lines, 1);
|
||||||
|
test_unterminated_line<T, multiline, quote>(lines, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const std::vector<std::string> lines{"9,8,first", "1,2,\"just",
|
||||||
|
"3,4,th\\,ird"};
|
||||||
|
test_unterminated_line<T, multiline, escape, quote>(lines, 1);
|
||||||
|
test_unterminated_line<T, multiline, quote>(lines, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const std::vector<std::string> lines{"9,8,first", "3,4,second",
|
||||||
|
"1,2,\"just"};
|
||||||
|
test_unterminated_line<T, multiline, escape, quote>(lines, 2);
|
||||||
|
test_unterminated_line<T, multiline, quote>(lines, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const std::vector<std::string> lines{"9,8,\"first\"",
|
||||||
|
"\"3\",4,\"sec,ond\"",
|
||||||
|
"1,2,\"ju\"\"st"};
|
||||||
|
test_unterminated_line<T, multiline, escape, quote>(lines, 2);
|
||||||
|
test_unterminated_line<T, multiline, quote>(lines, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// unterminated quote and escape
|
||||||
|
{
|
||||||
|
const std::vector<std::string> lines{"1,2,\"just\\"};
|
||||||
|
test_unterminated_line<T, multiline, escape, quote>(lines, 0);
|
||||||
|
test_unterminated_line<T, multiline, escape, quote>(lines, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const std::vector<std::string> lines{"1,2,\"just\\\n\\"};
|
||||||
|
test_unterminated_line<T, multiline, escape, quote>(lines, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const std::vector<std::string> lines{"1,2,\"just\n\\"};
|
||||||
|
test_unterminated_line<T, multiline, escape, quote>(lines, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const std::vector<std::string> lines{"9,8,first", "1,2,\"just\n\\"};
|
||||||
|
test_unterminated_line<T, multiline, escape, quote>(lines, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const std::vector<std::string> lines{"9,8,first", "1,2,\"just\n\\",
|
||||||
|
"4,3,thrid"};
|
||||||
|
test_unterminated_line<T, multiline, escape, quote>(lines, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const std::vector<std::string> lines{"9,8,f\\\nirst", "1,2,\"just\n\\",
|
||||||
|
"4,3,thrid"};
|
||||||
|
test_unterminated_line<T, multiline, escape, quote>(lines, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const std::vector<std::string> lines{"9,8,\"f\ni\nrst\"",
|
||||||
|
"1,2,\"just\n\\", "4,3,thrid"};
|
||||||
|
test_unterminated_line<T, multiline, escape, quote>(lines, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// multiline limmit reached escape
|
||||||
|
{
|
||||||
|
const std::vector<std::string> lines{"1,2,\\\n\\\n\\\n\\\njust"};
|
||||||
|
test_unterminated_line<T, multiline, escape>(lines, 0);
|
||||||
|
test_unterminated_line<T, multiline, escape, quote>(lines, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const std::vector<std::string> lines{"9,8,first",
|
||||||
|
"1,2,\\\n\\\n\\\n\\\njust"};
|
||||||
|
test_unterminated_line<T, multiline, escape>(lines, 1);
|
||||||
|
test_unterminated_line<T, multiline, escape, quote>(lines, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const std::vector<std::string> lines{"9,8,fi\\\nrs\\\nt",
|
||||||
|
"1,2,\\\n\\\n\\\n\\\njust"};
|
||||||
|
test_unterminated_line<T, multiline, escape>(lines, 1);
|
||||||
|
test_unterminated_line<T, multiline, escape, quote>(lines, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const std::vector<std::string> lines{"9,8,first",
|
||||||
|
"1,2,\\\n\\\n\\\n\\\njust",
|
||||||
|
"4,3,third"};
|
||||||
|
test_unterminated_line<T, multiline, escape>(lines, 1);
|
||||||
|
test_unterminated_line<T, multiline, escape, quote>(lines, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// multiline limmit reached quote
|
||||||
|
{
|
||||||
|
const std::vector<std::string> lines{"1,2,\"\n\n\n\n\njust\""};
|
||||||
|
test_unterminated_line<T, multiline, escape, quote>(lines, 0);
|
||||||
|
test_unterminated_line<T, multiline, quote>(lines, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const std::vector<std::string> lines{"9,8,first",
|
||||||
|
"1,2,\"\n\n\n\n\njust\""};
|
||||||
|
test_unterminated_line<T, multiline, escape, quote>(lines, 1);
|
||||||
|
test_unterminated_line<T, multiline, quote>(lines, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const std::vector<std::string> lines{"9,8,\"fir\nst\"",
|
||||||
|
"1,2,\"\n\n\n\n\njust\""};
|
||||||
|
test_unterminated_line<T, multiline, escape, quote>(lines, 1);
|
||||||
|
test_unterminated_line<T, multiline, quote>(lines, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// multiline limmit reached quote and escape
|
||||||
|
{
|
||||||
|
const std::vector<std::string> lines{"1,2,\"\\\n\n\\\n\\\n\\\njust"};
|
||||||
|
test_unterminated_line<T, multiline, escape, quote>(lines, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const std::vector<std::string> lines{"9,8,first",
|
||||||
|
"1,2,\"\\\n\n\\\n\\\n\\\njust"};
|
||||||
|
test_unterminated_line<T, multiline, escape, quote>(lines, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const std::vector<std::string> lines{"9,8,fi\\\nrst",
|
||||||
|
"1,2,\"\\\n\n\\\n\\\n\\\njust"};
|
||||||
|
test_unterminated_line<T, multiline, escape, quote>(lines, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const std::vector<std::string> lines{"9,8,\"fi\nrst\"",
|
||||||
|
"1,2,\"\\\n\n\\\n\\\n\\\njust"};
|
||||||
|
test_unterminated_line<T, multiline, escape, quote>(lines, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const std::vector<std::string> lines{"9,8,\"fi\nr\\\nst\"",
|
||||||
|
"1,2,\"\\\n\n\\\n\\\n\\\njust"};
|
||||||
|
test_unterminated_line<T, multiline, escape, quote>(lines, 1);
|
||||||
|
}
|
||||||
|
}
|
400
test/test_parser1_4.cpp
Normal file
400
test/test_parser1_4.cpp
Normal file
@ -0,0 +1,400 @@
|
|||||||
|
#include "test_parser1.hpp"
|
||||||
|
|
||||||
|
template <typename T, typename Tuple>
|
||||||
|
struct has_type;
|
||||||
|
|
||||||
|
template <typename T, typename... Us>
|
||||||
|
struct has_type<T, std::tuple<Us...>>
|
||||||
|
: std::disjunction<std::is_same<T, Us>...> {};
|
||||||
|
|
||||||
|
template <typename T, typename... Ts>
|
||||||
|
static void test_fields(const std::string file_name, const std::vector<X>& data,
|
||||||
|
const std::vector<std::string>& header,
|
||||||
|
const std::vector<std::string>& fields) {
|
||||||
|
constexpr auto buffer_mode = T::BufferMode::value;
|
||||||
|
using ErrorMode = typename T::ErrorMode;
|
||||||
|
using CaseType = std::tuple<Ts...>;
|
||||||
|
|
||||||
|
auto [p, _] = make_parser<buffer_mode, ErrorMode>(file_name, ",");
|
||||||
|
CHECK_FALSE(p.field_exists("Unknown"));
|
||||||
|
p.use_fields(fields);
|
||||||
|
|
||||||
|
CHECK_EQ_ARRAY(header, p.header());
|
||||||
|
CHECK_EQ(merge_header(p.header(), ","), p.raw_header());
|
||||||
|
std::vector<CaseType> i;
|
||||||
|
|
||||||
|
for (const auto& a : p.template iterate<CaseType>()) {
|
||||||
|
CHECK_EQ_ARRAY(header, p.header());
|
||||||
|
CHECK_EQ(merge_header(p.header(), ","), p.raw_header());
|
||||||
|
i.push_back(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK_EQ(i.size(), data.size());
|
||||||
|
for (size_t j = 0; j < i.size(); ++j) {
|
||||||
|
if constexpr (has_type<int, CaseType>::value) {
|
||||||
|
CHECK_EQ(std::get<int>(i[j]), data[j].i);
|
||||||
|
}
|
||||||
|
if constexpr (has_type<double, CaseType>::value) {
|
||||||
|
CHECK_EQ(std::get<double>(i[j]), data[j].d);
|
||||||
|
}
|
||||||
|
if constexpr (has_type<std::string, CaseType>::value) {
|
||||||
|
CHECK_EQ(std::get<std::string>(i[j]), data[j].s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_TEMPLATE("test various cases with header", T,
|
||||||
|
ParserOptionCombinations) {
|
||||||
|
unique_file_name f{"various_cases_with_header"};
|
||||||
|
using str = std::string;
|
||||||
|
|
||||||
|
constexpr static auto Int = "Int";
|
||||||
|
constexpr static auto Dbl = "Double";
|
||||||
|
constexpr static auto Str = "String";
|
||||||
|
const std::vector<std::string> header{Int, Dbl, Str};
|
||||||
|
|
||||||
|
std::vector<X> data = {{1, 2, "x"}, {3, 4, "y"}, {5, 6, "z"},
|
||||||
|
{7, 8, "u"}, {9, 10, "v"}, {11, 12, "w"}};
|
||||||
|
|
||||||
|
make_and_write(f.name, data, header);
|
||||||
|
const auto& o = f.name;
|
||||||
|
const auto& d = data;
|
||||||
|
|
||||||
|
{
|
||||||
|
ss::parser<ss::string_error> p{f.name, ","};
|
||||||
|
std::vector<X> i;
|
||||||
|
|
||||||
|
for (const auto& a : p.iterate<int, double, std::string>()) {
|
||||||
|
CHECK_EQ(header, p.header());
|
||||||
|
CHECK_EQ(merge_header(p.header(), ","), p.raw_header());
|
||||||
|
i.emplace_back(ss::to_object<X>(a));
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK_NE(i, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
ss::parser<ss::string_error> p{f.name, ","};
|
||||||
|
std::vector<X> i;
|
||||||
|
|
||||||
|
p.ignore_next();
|
||||||
|
for (const auto& a : p.iterate<int, double, std::string>()) {
|
||||||
|
CHECK_EQ(header, p.header());
|
||||||
|
CHECK_EQ(merge_header(p.header(), ","), p.raw_header());
|
||||||
|
i.emplace_back(ss::to_object<X>(a));
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK_EQ(i, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
ss::parser<ss::string_error> p{f.name, ","};
|
||||||
|
CHECK_EQ(header, p.header());
|
||||||
|
CHECK_EQ(merge_header(p.header(), ","), p.raw_header());
|
||||||
|
|
||||||
|
p.use_fields(Int, Dbl);
|
||||||
|
CHECK_EQ(header, p.header());
|
||||||
|
CHECK_EQ(merge_header(p.header(), ","), p.raw_header());
|
||||||
|
|
||||||
|
{
|
||||||
|
auto [int_, double_] = p.get_next<int, double>();
|
||||||
|
CHECK_EQ(int_, data[0].i);
|
||||||
|
CHECK_EQ(double_, data[0].d);
|
||||||
|
}
|
||||||
|
|
||||||
|
p.use_fields(Dbl, Int);
|
||||||
|
CHECK_EQ(header, p.header());
|
||||||
|
CHECK_EQ(merge_header(p.header(), ","), p.raw_header());
|
||||||
|
|
||||||
|
{
|
||||||
|
auto [double_, int_] = p.get_next<double, int>();
|
||||||
|
CHECK_EQ(int_, data[1].i);
|
||||||
|
CHECK_EQ(double_, data[1].d);
|
||||||
|
}
|
||||||
|
|
||||||
|
p.use_fields(Str);
|
||||||
|
|
||||||
|
{
|
||||||
|
auto string_ = p.get_next<std::string>();
|
||||||
|
CHECK_EQ(string_, data[2].s);
|
||||||
|
}
|
||||||
|
|
||||||
|
p.use_fields(Str, Int, Dbl);
|
||||||
|
|
||||||
|
{
|
||||||
|
auto [string_, int_, double_] =
|
||||||
|
p.get_next<std::string, int, double>();
|
||||||
|
CHECK_EQ(double_, data[3].d);
|
||||||
|
CHECK_EQ(int_, data[3].i);
|
||||||
|
CHECK_EQ(string_, data[3].s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* python used to generate permutations
|
||||||
|
import itertools
|
||||||
|
|
||||||
|
header = {'str': 'Str',
|
||||||
|
'double': 'Dbl',
|
||||||
|
'int': 'Int'}
|
||||||
|
|
||||||
|
keys = ['str', 'int', 'double']
|
||||||
|
|
||||||
|
for r in range (1, 3):
|
||||||
|
combinations = list(itertools.permutations(keys, r = r))
|
||||||
|
|
||||||
|
for combination in combinations:
|
||||||
|
template_params = []
|
||||||
|
arg_params = []
|
||||||
|
for type in combination:
|
||||||
|
template_params.append(type)
|
||||||
|
arg_params.append(header[type])
|
||||||
|
call = 'testFields<' + ', '.join(template_params) + \
|
||||||
|
'>(o, d, header, {' + ', '.join(arg_params) + '});'
|
||||||
|
print(call)
|
||||||
|
*/
|
||||||
|
|
||||||
|
test_fields<T, str>(o, d, header, {Str});
|
||||||
|
test_fields<T, int>(o, d, header, {Int});
|
||||||
|
test_fields<T, double>(o, d, header, {Dbl});
|
||||||
|
test_fields<T, str, int>(o, d, header, {Str, Int});
|
||||||
|
test_fields<T, str, double>(o, d, header, {Str, Dbl});
|
||||||
|
test_fields<T, int, str>(o, d, header, {Int, Str});
|
||||||
|
test_fields<T, int, double>(o, d, header, {Int, Dbl});
|
||||||
|
test_fields<T, double, str>(o, d, header, {Dbl, Str});
|
||||||
|
test_fields<T, double, int>(o, d, header, {Dbl, Int});
|
||||||
|
test_fields<T, str, int, double>(o, d, header, {Str, Int, Dbl});
|
||||||
|
test_fields<T, str, double, int>(o, d, header, {Str, Dbl, Int});
|
||||||
|
test_fields<T, int, str, double>(o, d, header, {Int, Str, Dbl});
|
||||||
|
test_fields<T, int, double, str>(o, d, header, {Int, Dbl, Str});
|
||||||
|
test_fields<T, double, str, int>(o, d, header, {Dbl, Str, Int});
|
||||||
|
test_fields<T, double, int, str>(o, d, header, {Dbl, Int, Str});
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void test_invalid_fields(const std::vector<std::string>& lines,
|
||||||
|
const std::vector<std::string>& fields) {
|
||||||
|
constexpr auto buffer_mode = T::BufferMode::value;
|
||||||
|
using ErrorMode = typename T::ErrorMode;
|
||||||
|
|
||||||
|
auto check_header = [&lines](auto& p) {
|
||||||
|
if (lines.empty()) {
|
||||||
|
CHECK_EQ(p.header().size(), 1);
|
||||||
|
CHECK_EQ(p.header().at(0), "");
|
||||||
|
CHECK_EQ(merge_header(p.header(), ","), p.raw_header());
|
||||||
|
} else {
|
||||||
|
CHECK_EQ(lines[0], merge_header(p.header()));
|
||||||
|
CHECK_EQ(merge_header(p.header(), ","), p.raw_header());
|
||||||
|
}
|
||||||
|
CHECK(p.valid());
|
||||||
|
};
|
||||||
|
|
||||||
|
unique_file_name f{"invalid_fields"};
|
||||||
|
{
|
||||||
|
std::ofstream out{f.name};
|
||||||
|
for (const auto& line : lines) {
|
||||||
|
out << line << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// No fields specified
|
||||||
|
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
|
||||||
|
auto command = [&p = p] { p.use_fields(); };
|
||||||
|
expect_error_on_command(p, command);
|
||||||
|
check_header(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Unknown field
|
||||||
|
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
|
||||||
|
auto command = [&p = p] { p.use_fields("Unknown"); };
|
||||||
|
expect_error_on_command(p, command);
|
||||||
|
check_header(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Field used multiple times
|
||||||
|
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
|
||||||
|
auto command = [&p = p, &fields = fields] {
|
||||||
|
p.use_fields(fields.at(0), fields.at(0));
|
||||||
|
};
|
||||||
|
if (!fields.empty()) {
|
||||||
|
expect_error_on_command(p, command);
|
||||||
|
}
|
||||||
|
check_header(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Mapping out of range
|
||||||
|
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
|
||||||
|
auto command = [&p = p, &fields = fields] {
|
||||||
|
p.use_fields(fields.at(0));
|
||||||
|
std::ignore = p.template get_next<std::string, std::string>();
|
||||||
|
};
|
||||||
|
check_header(p);
|
||||||
|
|
||||||
|
if (!fields.empty()) {
|
||||||
|
expect_error_on_command(p, command);
|
||||||
|
}
|
||||||
|
check_header(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Invalid header
|
||||||
|
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
|
||||||
|
auto command = [&p = p, &fields = fields] { p.use_fields(fields); };
|
||||||
|
check_header(p);
|
||||||
|
|
||||||
|
if (!fields.empty()) {
|
||||||
|
// Pass if there are no duplicates, fail otherwise
|
||||||
|
if (std::unordered_set<std::string>{fields.begin(), fields.end()}
|
||||||
|
.size() != fields.size()) {
|
||||||
|
expect_error_on_command(p, command);
|
||||||
|
} else {
|
||||||
|
command();
|
||||||
|
CHECK(p.valid());
|
||||||
|
if (!p.valid()) {
|
||||||
|
if constexpr (T::StringError) {
|
||||||
|
std::cout << p.error_msg() << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
check_header(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_TEMPLATE("test invalid header fields usage", T,
|
||||||
|
ParserOptionCombinations) {
|
||||||
|
test_invalid_fields<T>({}, {});
|
||||||
|
|
||||||
|
test_invalid_fields<T>({"Int"}, {"Int"});
|
||||||
|
test_invalid_fields<T>({"Int", "1"}, {"Int"});
|
||||||
|
test_invalid_fields<T>({"Int", "1", "2"}, {"Int"});
|
||||||
|
|
||||||
|
test_invalid_fields<T>({"Int,String"}, {"Int", "String"});
|
||||||
|
test_invalid_fields<T>({"Int,String", "1,hi"}, {"Int", "String"});
|
||||||
|
test_invalid_fields<T>({"Int,String", "2,hello"}, {"Int", "String"});
|
||||||
|
|
||||||
|
test_invalid_fields<T>({"Int,String,Double"}, {"Int", "String", "Double"});
|
||||||
|
test_invalid_fields<T>({"Int,String,Double", "1,hi,2.34"},
|
||||||
|
{"Int", "String", "Double"});
|
||||||
|
test_invalid_fields<T>({"Int,String,Double", "1,hi,2.34", "2,hello,3.45"},
|
||||||
|
{"Int", "String", "Double"});
|
||||||
|
|
||||||
|
test_invalid_fields<T>({"Int,Int,Int"}, {"Int", "Int", "Int"});
|
||||||
|
test_invalid_fields<T>({"Int,Int,Int", "1,2,3"}, {"Int", "Int", "Int"});
|
||||||
|
|
||||||
|
test_invalid_fields<T>({"Int,String,Int"}, {"Int", "String", "Int"});
|
||||||
|
test_invalid_fields<T>({"Int,String,Int", "1,hi,3"},
|
||||||
|
{"Int", "String", "Int"});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_TEMPLATE("test invalid rows with header", T,
|
||||||
|
ParserOptionCombinations) {
|
||||||
|
constexpr auto buffer_mode = T::BufferMode::value;
|
||||||
|
using ErrorMode = typename T::ErrorMode;
|
||||||
|
|
||||||
|
unique_file_name f{"invalid_rows_with_header"};
|
||||||
|
{
|
||||||
|
std::ofstream out{f.name};
|
||||||
|
out << "Int,String,Double" << std::endl;
|
||||||
|
out << "1,line1,2.34" << std::endl;
|
||||||
|
out << "2,line2" << std::endl;
|
||||||
|
out << "3,line3,67.8" << std::endl;
|
||||||
|
out << "4,line4,67.8,9" << std::endl;
|
||||||
|
out << "5,line5,9.10" << std::endl;
|
||||||
|
out << "six,line6,10.11" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> header = {"Int", "String", "Double"};
|
||||||
|
|
||||||
|
{
|
||||||
|
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name);
|
||||||
|
CHECK_EQ_ARRAY(header, p.header());
|
||||||
|
CHECK_EQ(merge_header(p.header()), p.raw_header());
|
||||||
|
|
||||||
|
p.use_fields("Int", "String", "Double");
|
||||||
|
using data = std::tuple<int, std::string, double>;
|
||||||
|
std::vector<data> i;
|
||||||
|
|
||||||
|
CHECK(p.valid());
|
||||||
|
|
||||||
|
while (!p.eof()) {
|
||||||
|
try {
|
||||||
|
const auto& t = p.template get_next<data>();
|
||||||
|
if (p.valid()) {
|
||||||
|
i.push_back(t);
|
||||||
|
}
|
||||||
|
} catch (const ss::exception&) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<data> expected = {{1, "line1", 2.34},
|
||||||
|
{3, "line3", 67.8},
|
||||||
|
{5, "line5", 9.10}};
|
||||||
|
CHECK_EQ(i, expected);
|
||||||
|
CHECK_EQ_ARRAY(header, p.header());
|
||||||
|
CHECK_EQ(merge_header(p.header()), p.raw_header());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name);
|
||||||
|
CHECK_EQ_ARRAY(header, p.header());
|
||||||
|
CHECK_EQ(merge_header(p.header()), p.raw_header());
|
||||||
|
|
||||||
|
p.use_fields("Double", "Int");
|
||||||
|
using data = std::tuple<double, int>;
|
||||||
|
std::vector<data> i;
|
||||||
|
|
||||||
|
CHECK(p.valid());
|
||||||
|
|
||||||
|
while (!p.eof()) {
|
||||||
|
try {
|
||||||
|
const auto& t = p.template get_next<data>();
|
||||||
|
if (p.valid()) {
|
||||||
|
i.push_back(t);
|
||||||
|
}
|
||||||
|
} catch (const ss::exception&) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<data> expected = {{2.34, 1}, {67.8, 3}, {9.10, 5}};
|
||||||
|
CHECK_EQ(i, expected);
|
||||||
|
CHECK_EQ_ARRAY(header, p.header());
|
||||||
|
CHECK_EQ(merge_header(p.header()), p.raw_header());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name);
|
||||||
|
CHECK_EQ_ARRAY(header, p.header());
|
||||||
|
CHECK_EQ(merge_header(p.header()), p.raw_header());
|
||||||
|
|
||||||
|
p.use_fields("String", "Double");
|
||||||
|
using data = std::tuple<std::string, double>;
|
||||||
|
std::vector<data> i;
|
||||||
|
|
||||||
|
CHECK(p.valid());
|
||||||
|
|
||||||
|
while (!p.eof()) {
|
||||||
|
try {
|
||||||
|
const auto& t = p.template get_next<data>();
|
||||||
|
if (p.valid()) {
|
||||||
|
i.push_back(t);
|
||||||
|
}
|
||||||
|
} catch (const ss::exception&) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<data> expected = {{"line1", 2.34},
|
||||||
|
{"line3", 67.8},
|
||||||
|
{"line5", 9.10},
|
||||||
|
{"line6", 10.11}};
|
||||||
|
CHECK_EQ(i, expected);
|
||||||
|
CHECK_EQ_ARRAY(header, p.header());
|
||||||
|
CHECK_EQ(merge_header(p.header()), p.raw_header());
|
||||||
|
}
|
||||||
|
}
|
301
test/test_parser1_5.cpp
Normal file
301
test/test_parser1_5.cpp
Normal file
@ -0,0 +1,301 @@
|
|||||||
|
#include "test_parser1.hpp"
|
||||||
|
|
||||||
|
TEST_CASE_TEMPLATE("test empty fields header", T, ParserOptionCombinations) {
|
||||||
|
constexpr auto buffer_mode = T::BufferMode::value;
|
||||||
|
using ErrorMode = typename T::ErrorMode;
|
||||||
|
|
||||||
|
unique_file_name f{"empty_fields_header"};
|
||||||
|
|
||||||
|
// Empty header
|
||||||
|
{
|
||||||
|
std::ofstream out{f.name};
|
||||||
|
out << "" << std::endl;
|
||||||
|
out << "1" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::vector<std::string> expected_header = {""};
|
||||||
|
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name);
|
||||||
|
CHECK_EQ_ARRAY(expected_header, p.header());
|
||||||
|
CHECK_EQ("", p.raw_header());
|
||||||
|
CHECK(p.valid());
|
||||||
|
}
|
||||||
|
|
||||||
|
// All empty header fields
|
||||||
|
{
|
||||||
|
std::ofstream out{f.name};
|
||||||
|
out << ",," << std::endl;
|
||||||
|
out << "1,2,3" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::vector<std::string> expected_header = {"", "", ""};
|
||||||
|
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name);
|
||||||
|
CHECK_EQ_ARRAY(expected_header, p.header());
|
||||||
|
CHECK_EQ(",,", p.raw_header());
|
||||||
|
CHECK(p.valid());
|
||||||
|
|
||||||
|
auto command1 = [&p = p] { std::ignore = p.field_exists("Int"); };
|
||||||
|
expect_error_on_command(p, command1);
|
||||||
|
|
||||||
|
auto command2 = [&p = p] { p.use_fields("Int"); };
|
||||||
|
expect_error_on_command(p, command2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// One empty field
|
||||||
|
const std::vector<std::string> valid_fields = {"Int0", "Int1", ""};
|
||||||
|
|
||||||
|
using svec = std::vector<std::string>;
|
||||||
|
const std::vector<std::vector<std::string>> valid_field_combinations =
|
||||||
|
{svec{"Int0"},
|
||||||
|
svec{"Int1"},
|
||||||
|
svec{""},
|
||||||
|
svec{"", "Int0"},
|
||||||
|
svec{"Int0", "Int1"},
|
||||||
|
svec{"Int1", ""},
|
||||||
|
svec{"Int0", "", "Int1"},
|
||||||
|
svec{"", "Int1", "Int0"}};
|
||||||
|
|
||||||
|
// Last header field empty
|
||||||
|
{
|
||||||
|
std::ofstream out{f.name};
|
||||||
|
out << "Int0,Int1," << std::endl;
|
||||||
|
out << "1,2,3" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::vector<std::string> expected_header = {"Int0", "Int1", ""};
|
||||||
|
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name);
|
||||||
|
CHECK_EQ_ARRAY(expected_header, p.header());
|
||||||
|
CHECK_EQ("Int0,Int1,", p.raw_header());
|
||||||
|
CHECK(p.valid());
|
||||||
|
|
||||||
|
for (const auto& field : valid_fields) {
|
||||||
|
CHECK(p.field_exists(field));
|
||||||
|
CHECK(p.valid());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& fields : valid_field_combinations) {
|
||||||
|
p.use_fields(fields);
|
||||||
|
CHECK(p.valid());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// First header field empty
|
||||||
|
{
|
||||||
|
std::ofstream out{f.name};
|
||||||
|
out << ",Int0,Int1" << std::endl;
|
||||||
|
out << "1,2,3" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::vector<std::string> expected_header = {"", "Int0", "Int1"};
|
||||||
|
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name);
|
||||||
|
CHECK_EQ_ARRAY(expected_header, p.header());
|
||||||
|
CHECK_EQ(",Int0,Int1", p.raw_header());
|
||||||
|
CHECK(p.valid());
|
||||||
|
|
||||||
|
for (const auto& field : valid_fields) {
|
||||||
|
CHECK(p.field_exists(field));
|
||||||
|
CHECK(p.valid());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& fields : valid_field_combinations) {
|
||||||
|
p.use_fields(fields);
|
||||||
|
CHECK(p.valid());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Middle header field empty
|
||||||
|
{
|
||||||
|
std::ofstream out{f.name};
|
||||||
|
out << "Int0,,Int1" << std::endl;
|
||||||
|
out << "1,2,3" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::vector<std::string> expected_header = {"Int0", "", "Int1"};
|
||||||
|
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name);
|
||||||
|
CHECK_EQ_ARRAY(expected_header, p.header());
|
||||||
|
CHECK_EQ("Int0,,Int1", p.raw_header());
|
||||||
|
CHECK(p.valid());
|
||||||
|
|
||||||
|
for (const auto& field : valid_fields) {
|
||||||
|
CHECK(p.field_exists(field));
|
||||||
|
CHECK(p.valid());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& fields : valid_field_combinations) {
|
||||||
|
p.use_fields(fields);
|
||||||
|
CHECK(p.valid());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename... Ts>
|
||||||
|
void test_unterminated_quote_header() {
|
||||||
|
constexpr auto buffer_mode = T::BufferMode::value;
|
||||||
|
using ErrorMode = typename T::ErrorMode;
|
||||||
|
|
||||||
|
unique_file_name f{"unterminated_quote_header"};
|
||||||
|
|
||||||
|
{
|
||||||
|
std::ofstream out{f.name};
|
||||||
|
out << "\"Int" << std::endl;
|
||||||
|
out << "1" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto [p, _] = make_parser<buffer_mode, ErrorMode, Ts...>(f.name);
|
||||||
|
|
||||||
|
auto command0 = [&p = p] { std::ignore = p.header(); };
|
||||||
|
expect_error_on_command(p, command0);
|
||||||
|
CHECK_EQ(p.raw_header(), "\"Int");
|
||||||
|
|
||||||
|
auto command1 = [&p = p] { std::ignore = p.field_exists("Int"); };
|
||||||
|
expect_error_on_command(p, command1);
|
||||||
|
|
||||||
|
auto command2 = [&p = p] { p.use_fields("Int"); };
|
||||||
|
expect_error_on_command(p, command2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_TEMPLATE("test unterminated quote header", T,
|
||||||
|
ParserOptionCombinations) {
|
||||||
|
using quote = ss::quote<'"'>;
|
||||||
|
using escape = ss::escape<'\\'>;
|
||||||
|
test_unterminated_quote_header<T, quote>();
|
||||||
|
test_unterminated_quote_header<T, quote, ss::multiline>();
|
||||||
|
test_unterminated_quote_header<T, quote, escape>();
|
||||||
|
test_unterminated_quote_header<T, quote, escape, ss::multiline>();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename... Ts>
|
||||||
|
void test_unterminated_escape_header() {
|
||||||
|
constexpr auto buffer_mode = T::BufferMode::value;
|
||||||
|
using ErrorMode = typename T::ErrorMode;
|
||||||
|
|
||||||
|
unique_file_name f{"unterminated_escape_header"};
|
||||||
|
|
||||||
|
// Unterminated escape in header
|
||||||
|
{
|
||||||
|
std::ofstream out{f.name};
|
||||||
|
out << "Int\\" << std::endl;
|
||||||
|
out << "1" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto [p, _] = make_parser<buffer_mode, ErrorMode, Ts...>(f.name);
|
||||||
|
|
||||||
|
auto command0 = [&p = p] { std::ignore = p.header(); };
|
||||||
|
expect_error_on_command(p, command0);
|
||||||
|
CHECK_EQ(p.raw_header(), "Int\\");
|
||||||
|
|
||||||
|
auto command1 = [&p = p] { std::ignore = p.field_exists("Int"); };
|
||||||
|
expect_error_on_command(p, command1);
|
||||||
|
|
||||||
|
auto command2 = [&p = p] { p.use_fields("Int"); };
|
||||||
|
expect_error_on_command(p, command2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_TEMPLATE("test unterminated escape header", T,
|
||||||
|
ParserOptionCombinations) {
|
||||||
|
using quote = ss::quote<'"'>;
|
||||||
|
using escape = ss::escape<'\\'>;
|
||||||
|
test_unterminated_escape_header<T, escape>();
|
||||||
|
test_unterminated_escape_header<T, escape, ss::multiline>();
|
||||||
|
test_unterminated_escape_header<T, escape, quote>();
|
||||||
|
test_unterminated_escape_header<T, escape, quote, ss::multiline>();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void test_ignore_empty(const std::vector<X>& data) {
|
||||||
|
constexpr auto buffer_mode = T::BufferMode::value;
|
||||||
|
using ErrorMode = typename T::ErrorMode;
|
||||||
|
|
||||||
|
unique_file_name f{"ignore_empty"};
|
||||||
|
make_and_write(f.name, data);
|
||||||
|
|
||||||
|
std::vector<X> expected;
|
||||||
|
for (const auto& d : data) {
|
||||||
|
if (d.s != X::empty) {
|
||||||
|
expected.push_back(d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto [p, _] =
|
||||||
|
make_parser<buffer_mode, ErrorMode, ss::ignore_empty>(f.name, ",");
|
||||||
|
|
||||||
|
std::vector<X> i;
|
||||||
|
for (const auto& a : p.template iterate<X>()) {
|
||||||
|
i.push_back(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK_EQ(i, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
|
||||||
|
std::vector<X> i;
|
||||||
|
size_t n = 0;
|
||||||
|
while (!p.eof()) {
|
||||||
|
try {
|
||||||
|
++n;
|
||||||
|
const auto& a = p.template get_next<X>();
|
||||||
|
if (data.at(n - 1).s == X::empty) {
|
||||||
|
CHECK_FALSE(p.valid());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
i.push_back(a);
|
||||||
|
} catch (...) {
|
||||||
|
CHECK_EQ(data.at(n - 1).s, X::empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK_EQ(i, expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_TEMPLATE("test various cases with empty lines", T,
|
||||||
|
ParserOptionCombinations) {
|
||||||
|
test_ignore_empty<T>(
|
||||||
|
{{1, 2, "x"}, {3, 4, "y"}, {9, 10, "v"}, {11, 12, "w"}});
|
||||||
|
|
||||||
|
test_ignore_empty<T>(
|
||||||
|
{{1, 2, X::empty}, {3, 4, "y"}, {9, 10, "v"}, {11, 12, "w"}});
|
||||||
|
|
||||||
|
test_ignore_empty<T>(
|
||||||
|
{{1, 2, "x"}, {3, 4, "y"}, {9, 10, "v"}, {11, 12, X::empty}});
|
||||||
|
|
||||||
|
test_ignore_empty<T>(
|
||||||
|
{{1, 2, "x"}, {5, 6, X::empty}, {9, 10, "v"}, {11, 12, "w"}});
|
||||||
|
|
||||||
|
test_ignore_empty<T>(
|
||||||
|
{{1, 2, X::empty}, {5, 6, X::empty}, {9, 10, "v"}, {11, 12, "w"}});
|
||||||
|
|
||||||
|
test_ignore_empty<T>(
|
||||||
|
{{1, 2, X::empty}, {3, 4, "y"}, {9, 10, "v"}, {11, 12, X::empty}});
|
||||||
|
|
||||||
|
test_ignore_empty<T>(
|
||||||
|
{{1, 2, "x"}, {3, 4, "y"}, {9, 10, X::empty}, {11, 12, X::empty}});
|
||||||
|
|
||||||
|
test_ignore_empty<T>(
|
||||||
|
{{1, 2, X::empty}, {3, 4, "y"}, {9, 10, X::empty}, {11, 12, X::empty}});
|
||||||
|
|
||||||
|
test_ignore_empty<T>({{1, 2, X::empty},
|
||||||
|
{3, 4, X::empty},
|
||||||
|
{9, 10, X::empty},
|
||||||
|
{11, 12, X::empty}});
|
||||||
|
|
||||||
|
test_ignore_empty<T>(
|
||||||
|
{{1, 2, "x"}, {3, 4, X::empty}, {9, 10, X::empty}, {11, 12, X::empty}});
|
||||||
|
|
||||||
|
test_ignore_empty<T>(
|
||||||
|
{{1, 2, X::empty}, {3, 4, X::empty}, {9, 10, X::empty}, {11, 12, "w"}});
|
||||||
|
|
||||||
|
test_ignore_empty<T>({{11, 12, X::empty}});
|
||||||
|
|
||||||
|
test_ignore_empty<T>({});
|
||||||
|
}
|
673
test/test_parser2.hpp
Normal file
673
test/test_parser2.hpp
Normal file
@ -0,0 +1,673 @@
|
|||||||
|
#include "test_helpers.hpp"
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <ss/parser.hpp>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
#ifndef SEGMENT_NAME
|
||||||
|
#error "SEGMENT_NAME must be defined"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// parser tests v2
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
struct random_number_generator {
|
||||||
|
size_t z1 = 12341;
|
||||||
|
size_t z2 = 12342;
|
||||||
|
size_t z3 = 12343;
|
||||||
|
size_t z4 = 12344;
|
||||||
|
|
||||||
|
size_t rand() {
|
||||||
|
uint32_t b;
|
||||||
|
b = ((z1 << 6) ^ z1) >> 13;
|
||||||
|
z1 = ((z1 & 4294967294U) << 18) ^ b;
|
||||||
|
b = ((z2 << 2) ^ z2) >> 27;
|
||||||
|
z2 = ((z2 & 4294967288U) << 2) ^ b;
|
||||||
|
b = ((z3 << 13) ^ z3) >> 21;
|
||||||
|
z3 = ((z3 & 4294967280U) << 7) ^ b;
|
||||||
|
b = ((z4 << 3) ^ z4) >> 12;
|
||||||
|
z4 = ((z4 & 4294967168U) << 13) ^ b;
|
||||||
|
return (z1 ^ z2 ^ z3 ^ z4);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
size_t rand_index(const T& s) {
|
||||||
|
REQUIRE(!s.empty());
|
||||||
|
return rand() % s.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rand_bool() {
|
||||||
|
return (rand() % 100) > 50;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void rand_insert(std::string& dst, const T& src) {
|
||||||
|
dst.insert(rand_index(dst), std::string{src});
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void rand_insert_n(std::string& dst, const T& src, size_t n_max) {
|
||||||
|
size_t n = rand() % n_max;
|
||||||
|
for (size_t i = 0; i < n; ++i) {
|
||||||
|
rand_insert(dst, src);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} rng;
|
||||||
|
|
||||||
|
struct field {
|
||||||
|
std::string value;
|
||||||
|
bool is_string = false;
|
||||||
|
bool has_spaces_left = false;
|
||||||
|
bool has_spaces_right = false;
|
||||||
|
bool has_new_line = false;
|
||||||
|
|
||||||
|
field(const std::string& input) {
|
||||||
|
value = input;
|
||||||
|
is_string = true;
|
||||||
|
|
||||||
|
has_spaces_left = !input.empty() && input.front() == ' ';
|
||||||
|
has_spaces_right = !input.empty() && input.back() == ' ';
|
||||||
|
has_new_line = input.find_first_of('\n') != std::string::npos;
|
||||||
|
}
|
||||||
|
|
||||||
|
field(int input) {
|
||||||
|
value = std::to_string(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
field(double input) {
|
||||||
|
value = std::to_string(input);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct column {
|
||||||
|
std::string header;
|
||||||
|
std::vector<field> fields;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename... Ts>
|
||||||
|
[[nodiscard]] column make_column(const std::string& input_header,
|
||||||
|
const std::vector<field>& input_fields) {
|
||||||
|
using setup = ss::setup<Ts...>;
|
||||||
|
std::vector<field> filtered_fields;
|
||||||
|
|
||||||
|
for (const auto& el : input_fields) {
|
||||||
|
if (!setup::multiline::enabled && el.has_new_line) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!setup::escape::enabled && !setup::quote::enabled) {
|
||||||
|
if (setup::trim_left::enabled && el.has_spaces_left) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setup::trim_right::enabled && el.has_spaces_right) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filtered_fields.push_back(el);
|
||||||
|
}
|
||||||
|
|
||||||
|
column c;
|
||||||
|
c.header = input_header;
|
||||||
|
c.fields = filtered_fields;
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[maybe_unused]] void replace_all2(std::string& s, const std::string& old_value,
|
||||||
|
const std::string& new_value) {
|
||||||
|
for (size_t i = 0; i < 999; ++i) {
|
||||||
|
size_t pos = s.find(old_value);
|
||||||
|
if (pos == std::string::npos) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
s.replace(pos, old_value.size(), new_value);
|
||||||
|
}
|
||||||
|
FAIL("bad replace");
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... Ts>
|
||||||
|
[[nodiscard]] std::vector<std::string> generate_csv_data(
|
||||||
|
const std::vector<field>& data, const std::string& delim) {
|
||||||
|
(void)delim;
|
||||||
|
using setup = ss::setup<Ts...>;
|
||||||
|
constexpr static auto escape = '\\';
|
||||||
|
constexpr static auto quote = '"';
|
||||||
|
constexpr static auto space = ' ';
|
||||||
|
constexpr static auto new_line = '\n';
|
||||||
|
constexpr static auto helper0 = '#';
|
||||||
|
constexpr static auto helper1 = '$';
|
||||||
|
// constexpr static auto helper3 = '&';
|
||||||
|
|
||||||
|
std::vector<std::string> output;
|
||||||
|
|
||||||
|
if (setup::escape::enabled && setup::quote::enabled) {
|
||||||
|
for (const auto& el : data) {
|
||||||
|
auto value = el.value;
|
||||||
|
|
||||||
|
replace_all2(value, {escape, quote}, {helper1});
|
||||||
|
|
||||||
|
bool quote_newline = rng.rand_bool();
|
||||||
|
bool quote_spacings = rng.rand_bool();
|
||||||
|
bool has_spaces = el.has_spaces_right || el.has_spaces_left;
|
||||||
|
|
||||||
|
// handle escape
|
||||||
|
replace_all2(value, {escape}, {helper0});
|
||||||
|
rng.rand_insert_n(value, escape, 2);
|
||||||
|
if (!quote_newline) {
|
||||||
|
replace_all2(value, {new_line}, {helper1});
|
||||||
|
replace_all2(value, {helper1}, {escape, new_line});
|
||||||
|
}
|
||||||
|
replace_all2(value, {escape, escape}, {escape});
|
||||||
|
replace_all2(value, {escape, helper0}, {helper0});
|
||||||
|
replace_all2(value, {helper0, escape}, {helper0});
|
||||||
|
replace_all2(value, {helper0}, {escape, escape});
|
||||||
|
|
||||||
|
replace_all2(value, {helper1}, {escape, quote});
|
||||||
|
|
||||||
|
replace_all2(value, {escape, quote}, {helper1});
|
||||||
|
|
||||||
|
if (rng.rand_bool() || quote_newline ||
|
||||||
|
(quote_spacings && has_spaces)) {
|
||||||
|
replace_all2(value, {quote}, {helper0});
|
||||||
|
if (rng.rand_bool()) {
|
||||||
|
replace_all2(value, {helper0}, {escape, quote});
|
||||||
|
} else {
|
||||||
|
replace_all2(value, {helper0}, {quote, quote});
|
||||||
|
}
|
||||||
|
value = std::string{quote} + value + std::string{quote};
|
||||||
|
}
|
||||||
|
|
||||||
|
replace_all2(value, {helper1}, {escape, quote});
|
||||||
|
|
||||||
|
if (!quote_spacings && has_spaces) {
|
||||||
|
replace_all2(value, {escape, space}, {helper0});
|
||||||
|
replace_all2(value, {space}, {helper0});
|
||||||
|
replace_all2(value, {helper0}, {escape, space});
|
||||||
|
}
|
||||||
|
|
||||||
|
output.push_back(value);
|
||||||
|
}
|
||||||
|
} else if (setup::escape::enabled) {
|
||||||
|
for (const auto& el : data) {
|
||||||
|
auto value = el.value;
|
||||||
|
|
||||||
|
replace_all2(value, {escape}, {helper0});
|
||||||
|
rng.rand_insert_n(value, escape, 3);
|
||||||
|
replace_all2(value, {new_line}, {helper1});
|
||||||
|
replace_all2(value, {helper1}, {escape, new_line});
|
||||||
|
|
||||||
|
replace_all2(value, {escape, escape}, {escape});
|
||||||
|
replace_all2(value, {escape, helper0}, {helper0});
|
||||||
|
|
||||||
|
replace_all2(value, {helper0, escape}, {helper0});
|
||||||
|
replace_all2(value, {helper0}, {escape, escape});
|
||||||
|
|
||||||
|
if (setup::trim_right::enabled || setup::trim_left::enabled) {
|
||||||
|
// escape space
|
||||||
|
replace_all2(value, {escape, space}, {helper0});
|
||||||
|
replace_all2(value, {space}, {helper0});
|
||||||
|
replace_all2(value, {helper0}, {escape, space});
|
||||||
|
}
|
||||||
|
|
||||||
|
output.push_back(value);
|
||||||
|
}
|
||||||
|
} else if (setup::quote::enabled) {
|
||||||
|
for (const auto& el : data) {
|
||||||
|
auto value = el.value;
|
||||||
|
if (rng.rand_bool() || el.has_new_line || el.has_spaces_left ||
|
||||||
|
el.has_spaces_right) {
|
||||||
|
replace_all2(value, {quote}, {helper0});
|
||||||
|
replace_all2(value, {helper0}, {quote, quote});
|
||||||
|
value = std::string{quote} + value + std::string{quote};
|
||||||
|
}
|
||||||
|
output.push_back(value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (const auto& el : data) {
|
||||||
|
output.push_back(el.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setup::trim_right::enabled) {
|
||||||
|
for (auto& el : output) {
|
||||||
|
size_t n = rng.rand();
|
||||||
|
for (size_t i = 0; i < n % 3; ++i) {
|
||||||
|
el = el + " ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setup::trim_left::enabled) {
|
||||||
|
for (auto& el : output) {
|
||||||
|
size_t n = rng.rand();
|
||||||
|
for (size_t i = 0; i < n % 3; ++i) {
|
||||||
|
el = " " + el;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[maybe_unused]] void write_to_file(const std::vector<std::string>& data,
|
||||||
|
const std::string& delim,
|
||||||
|
const std::string& file_name,
|
||||||
|
bool add_new_line = true) {
|
||||||
|
std::ofstream out{file_name, std::ios_base::app};
|
||||||
|
std::string line;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < data.size(); ++i) {
|
||||||
|
line += data[i];
|
||||||
|
if (i != data.size() - 1) {
|
||||||
|
line += delim;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out << line;
|
||||||
|
if (add_new_line) {
|
||||||
|
out << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#define CHECK_EQ_CRLF(V1, V2) \
|
||||||
|
if (V1 != V2) { \
|
||||||
|
auto tmp1 = V1; \
|
||||||
|
auto tmp2 = V2; \
|
||||||
|
replace_all2(tmp1, "\r\n", "\n"); \
|
||||||
|
replace_all2(tmp2, "\r\n", "\n"); \
|
||||||
|
\
|
||||||
|
CHECK(tmp1 == tmp2); \
|
||||||
|
\
|
||||||
|
if (tmp1 != tmp2) { \
|
||||||
|
replace_all2(tmp1, "\r", "(r)"); \
|
||||||
|
replace_all2(tmp2, "\r", "(r)"); \
|
||||||
|
\
|
||||||
|
replace_all2(tmp1, "\n", "(n)"); \
|
||||||
|
replace_all2(tmp2, "\n", "(n)"); \
|
||||||
|
\
|
||||||
|
replace_all2(tmp1, " ", "_"); \
|
||||||
|
replace_all2(tmp2, " ", "_"); \
|
||||||
|
\
|
||||||
|
std::cout << "<" << tmp1 << ">" << std::endl; \
|
||||||
|
std::cout << "<" << tmp2 << ">" << std::endl; \
|
||||||
|
std::cout << "file: " << f.name << std::endl; \
|
||||||
|
std::cout << "----------------" << std::endl; \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
} else { \
|
||||||
|
CHECK(V1 == V2); \
|
||||||
|
}
|
||||||
|
|
||||||
|
template <bool buffer_mode, typename... Ts>
|
||||||
|
void test_data_combinations(const std::vector<column>& input_data,
|
||||||
|
const std::string& delim, bool include_header) {
|
||||||
|
using setup = ss::setup<Ts...>;
|
||||||
|
|
||||||
|
if (setup::ignore_header && !include_header) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
unique_file_name f{"parser_data_combinations" + std::string{SEGMENT_NAME}};
|
||||||
|
std::vector<std::vector<field>> expected_data;
|
||||||
|
std::vector<std::string> header;
|
||||||
|
std::vector<field> field_header;
|
||||||
|
|
||||||
|
auto add_blank_if_ignore_empty = [&] {
|
||||||
|
if constexpr (setup::ignore_empty) {
|
||||||
|
size_t n = rng.rand() % 3;
|
||||||
|
for (size_t i = 0; i < n; ++i) {
|
||||||
|
write_to_file({}, delim, f.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& el : input_data) {
|
||||||
|
header.push_back(el.header);
|
||||||
|
field_header.push_back(field{el.header});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string header_line;
|
||||||
|
if (include_header) {
|
||||||
|
auto header_data = generate_csv_data<Ts...>(field_header, delim);
|
||||||
|
header_line = merge_header(header_data, delim);
|
||||||
|
if (input_data.size() == 0 && rand() % 10 == 0) {
|
||||||
|
write_to_file(header_data, delim, f.name, false);
|
||||||
|
} else {
|
||||||
|
write_to_file(header_data, delim, f.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<int> layout;
|
||||||
|
size_t n = 1 + rng.rand() % 5;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < input_data.size(); ++i) {
|
||||||
|
layout.push_back(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < n; ++i) {
|
||||||
|
std::vector<field> raw_data;
|
||||||
|
for (const auto& el : input_data) {
|
||||||
|
const auto& fields = el.fields;
|
||||||
|
if (fields.empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
raw_data.push_back(fields[rng.rand_index(fields)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
add_blank_if_ignore_empty();
|
||||||
|
|
||||||
|
expected_data.push_back(raw_data);
|
||||||
|
auto data = generate_csv_data<Ts...>(raw_data, delim);
|
||||||
|
|
||||||
|
if (i + 1 == n && rand() % 10 == 0) {
|
||||||
|
write_to_file(data, delim, f.name, false);
|
||||||
|
} else {
|
||||||
|
write_to_file(data, delim, f.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto layout_combinations = include_header && !setup::ignore_header
|
||||||
|
? vector_combinations(layout, layout.size())
|
||||||
|
: std::vector<std::vector<int>>{layout};
|
||||||
|
|
||||||
|
auto remove_duplicates = [](const auto& vec) {
|
||||||
|
std::vector<int> unique_vec;
|
||||||
|
std::unordered_set<int> vec_set;
|
||||||
|
for (const auto& el : vec) {
|
||||||
|
if (vec_set.find(el) == vec_set.end()) {
|
||||||
|
vec_set.insert(el);
|
||||||
|
unique_vec.push_back(el);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return unique_vec;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::vector<int>> unique_layout_combinations;
|
||||||
|
for (const auto& layout : layout_combinations) {
|
||||||
|
unique_layout_combinations.push_back(remove_duplicates(layout));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& layout : unique_layout_combinations) {
|
||||||
|
auto [p, _] = make_parser<buffer_mode, setup>(f.name, delim);
|
||||||
|
|
||||||
|
if (include_header && !setup::ignore_header) {
|
||||||
|
std::vector<std::string> fields;
|
||||||
|
for (const auto& index : layout) {
|
||||||
|
fields.push_back(header[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if constexpr (!setup::ignore_header) {
|
||||||
|
p.use_fields(fields);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!p.valid()) {
|
||||||
|
if constexpr (setup::string_error) {
|
||||||
|
std::cout << p.error_msg() << std::endl;
|
||||||
|
} else {
|
||||||
|
std::cout << "use_fields failed" << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
REQUIRE(p.valid());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto check_error = [&p = p] {
|
||||||
|
CHECK(p.valid());
|
||||||
|
if (!p.valid()) {
|
||||||
|
if constexpr (setup::string_error) {
|
||||||
|
std::cout << p.error_msg() << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
auto check_header = [&p = p, &header = header, include_header,
|
||||||
|
header_line] {
|
||||||
|
if (include_header) {
|
||||||
|
if constexpr (!setup::ignore_header) {
|
||||||
|
CHECK_EQ_ARRAY(header, p.header());
|
||||||
|
CHECK_EQ(header_line, p.raw_header());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int num_columns = layout.size();
|
||||||
|
for (size_t i = 0; i < n + 1; ++i) {
|
||||||
|
check_header();
|
||||||
|
try {
|
||||||
|
switch (num_columns) {
|
||||||
|
case 1: {
|
||||||
|
auto s0 = p.template get_next<std::string>();
|
||||||
|
if (i < n) {
|
||||||
|
check_error();
|
||||||
|
CHECK_EQ_CRLF(s0, expected_data[i][layout[0]].value);
|
||||||
|
} else {
|
||||||
|
CHECK(p.eof());
|
||||||
|
CHECK(!p.valid());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 2: {
|
||||||
|
auto [s0, s1] =
|
||||||
|
p.template get_next<std::string, std::string>();
|
||||||
|
if (i < n) {
|
||||||
|
check_error();
|
||||||
|
CHECK_EQ_CRLF(s0, expected_data[i][layout[0]].value);
|
||||||
|
CHECK_EQ_CRLF(s1, expected_data[i][layout[1]].value);
|
||||||
|
} else {
|
||||||
|
CHECK(p.eof());
|
||||||
|
CHECK(!p.valid());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 3: {
|
||||||
|
auto [s0, s1, s2] =
|
||||||
|
p.template get_next<std::string, std::string,
|
||||||
|
std::string>();
|
||||||
|
if (i < n) {
|
||||||
|
check_error();
|
||||||
|
CHECK_EQ_CRLF(s0, expected_data[i][layout[0]].value);
|
||||||
|
CHECK_EQ_CRLF(s1, expected_data[i][layout[1]].value);
|
||||||
|
CHECK_EQ_CRLF(s2, expected_data[i][layout[2]].value);
|
||||||
|
} else {
|
||||||
|
CHECK(p.eof());
|
||||||
|
CHECK(!p.valid());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 4: {
|
||||||
|
auto [s0, s1, s2, s3] =
|
||||||
|
p.template get_next<std::string, std::string,
|
||||||
|
std::string, std::string>();
|
||||||
|
if (i < n) {
|
||||||
|
check_error();
|
||||||
|
CHECK_EQ_CRLF(s0, expected_data[i][layout[0]].value);
|
||||||
|
CHECK_EQ_CRLF(s1, expected_data[i][layout[1]].value);
|
||||||
|
CHECK_EQ_CRLF(s2, expected_data[i][layout[2]].value);
|
||||||
|
CHECK_EQ_CRLF(s3, expected_data[i][layout[3]].value);
|
||||||
|
} else {
|
||||||
|
CHECK(p.eof());
|
||||||
|
CHECK(!p.valid());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 5: {
|
||||||
|
auto [s0, s1, s2, s3, s4] =
|
||||||
|
p.template get_next<std::string, std::string,
|
||||||
|
std::string, std::string,
|
||||||
|
std::string>();
|
||||||
|
if (i < n) {
|
||||||
|
check_error();
|
||||||
|
CHECK_EQ_CRLF(s0, expected_data[i][layout[0]].value);
|
||||||
|
CHECK_EQ_CRLF(s1, expected_data[i][layout[1]].value);
|
||||||
|
CHECK_EQ_CRLF(s2, expected_data[i][layout[2]].value);
|
||||||
|
CHECK_EQ_CRLF(s3, expected_data[i][layout[3]].value);
|
||||||
|
CHECK_EQ_CRLF(s4, expected_data[i][layout[4]].value);
|
||||||
|
} else {
|
||||||
|
CHECK(p.eof());
|
||||||
|
CHECK(!p.valid());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
FAIL(("Invalid number of columns: " +
|
||||||
|
std::to_string(num_columns)));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
if (i < n) {
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... Ts>
|
||||||
|
void test_option_combinations() {
|
||||||
|
column ints0 =
|
||||||
|
make_column<Ts...>("ints0", {field{123}, field{45}, field{6}});
|
||||||
|
column ints1 =
|
||||||
|
make_column<Ts...>("ints1", {field{123}, field{45}, field{6}});
|
||||||
|
column ints2 =
|
||||||
|
make_column<Ts...>("ints2", {field{123}, field{45}, field{6}});
|
||||||
|
|
||||||
|
column floats0 =
|
||||||
|
make_column<Ts...>("floats0", {field{1.23}, field{456.7}, field{0.8},
|
||||||
|
field{910}, field{123456789.987654321}});
|
||||||
|
column floats1 =
|
||||||
|
make_column<Ts...>("floats1", {field{1.23}, field{456.7}, field{0.8},
|
||||||
|
field{910}, field{123456789.987654321}});
|
||||||
|
column floats2 =
|
||||||
|
make_column<Ts...>("floats2", {field{1.23}, field{456.7}, field{0.8},
|
||||||
|
field{910}, field{123456789.987654321}});
|
||||||
|
|
||||||
|
column strings0 =
|
||||||
|
make_column<Ts...>("strings0", {field{"just"}, field{"some"},
|
||||||
|
field{"random"}, field{"string"}});
|
||||||
|
|
||||||
|
column strings1 =
|
||||||
|
make_column<Ts...>("strings1", {field{"st\"rings"}, field{"w\"\"ith"},
|
||||||
|
field{"qu\"otes\\"}, field{"\\a\\n\\d"},
|
||||||
|
field{"escapes\""}});
|
||||||
|
|
||||||
|
column strings2 =
|
||||||
|
make_column<Ts...>("strings2",
|
||||||
|
{field{" with "}, field{" spaces"},
|
||||||
|
field{"and "}, field{"\nnew"}, field{" \nlines"},
|
||||||
|
field{" a\n\nn\n\nd "}, field{" \nso\n "},
|
||||||
|
field{"on"}});
|
||||||
|
|
||||||
|
auto columns0 = std::vector{ints0, strings0, floats0, strings1, strings2};
|
||||||
|
auto columns1 = std::vector{strings2, strings1, floats0, strings0, ints0};
|
||||||
|
auto columns2 = std::vector{floats0, strings1, ints0, strings2, strings0};
|
||||||
|
auto columns3 = std::vector{ints0, ints1, ints2};
|
||||||
|
auto columns4 = std::vector{floats0, floats1, floats2};
|
||||||
|
auto columns5 = std::vector{strings1, strings2};
|
||||||
|
auto columns6 = std::vector{strings1};
|
||||||
|
auto columns7 = std::vector{strings2};
|
||||||
|
|
||||||
|
for (size_t i = 0; i < 3; ++i) {
|
||||||
|
for (const auto& delimiter : {",", "-", "--"}) {
|
||||||
|
for (const auto& columns :
|
||||||
|
{columns0, columns1, columns2, columns3, columns4, columns5,
|
||||||
|
columns6, columns7}) {
|
||||||
|
try {
|
||||||
|
test_data_combinations<false, Ts...>(columns, delimiter,
|
||||||
|
false);
|
||||||
|
test_data_combinations<false, Ts...>(columns, delimiter,
|
||||||
|
true);
|
||||||
|
test_data_combinations<true, Ts...>(columns, delimiter,
|
||||||
|
false);
|
||||||
|
test_data_combinations<true, Ts...>(columns, delimiter,
|
||||||
|
true);
|
||||||
|
} catch (std::exception& e) {
|
||||||
|
std::cout << typeid(ss::parser<Ts...>).name() << std::endl;
|
||||||
|
FAIL_CHECK(std::string{e.what()});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... Ts>
|
||||||
|
void test_option_combinations0() {
|
||||||
|
test_option_combinations<Ts...>();
|
||||||
|
#ifdef CMAKE_GITHUB_CI
|
||||||
|
test_option_combinations<Ts..., ss::ignore_empty>();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... Ts>
|
||||||
|
void test_option_combinations1() {
|
||||||
|
test_option_combinations0<Ts...>();
|
||||||
|
#ifdef CMAKE_GITHUB_CI
|
||||||
|
test_option_combinations0<Ts..., ss::ignore_header>();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... Ts>
|
||||||
|
void test_option_combinations2() {
|
||||||
|
test_option_combinations1<Ts...>();
|
||||||
|
#ifdef CMAKE_GITHUB_CI
|
||||||
|
test_option_combinations1<Ts..., ss::string_error>();
|
||||||
|
test_option_combinations1<Ts..., ss::throw_on_error>();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... Ts>
|
||||||
|
void test_option_combinations3() {
|
||||||
|
using trim = ss::trim<' '>;
|
||||||
|
|
||||||
|
test_option_combinations2<Ts...>();
|
||||||
|
test_option_combinations2<Ts..., trim>();
|
||||||
|
}
|
||||||
|
|
||||||
|
} /* anonymous namespace */
|
||||||
|
|
||||||
|
// Tests split into multiple compilation units
|
||||||
|
#if 0
|
||||||
|
|
||||||
|
TEST_CASE("parser test various cases version 2 segment 1") {
|
||||||
|
using quote = ss::quote<'"'>;
|
||||||
|
using escape = ss::escape<'\\'>;
|
||||||
|
using multiline = ss::multiline;
|
||||||
|
|
||||||
|
#ifdef CMAKE_GITHUB_CI
|
||||||
|
using multiline_r = ss::multiline_restricted<10>;
|
||||||
|
using trimr = ss::trim_right<' '>;
|
||||||
|
using triml = ss::trim_left<' '>;
|
||||||
|
using trim = ss::trim<' '>;
|
||||||
|
|
||||||
|
// segment 1
|
||||||
|
test_option_combinations3<>();
|
||||||
|
test_option_combinations3<escape>();
|
||||||
|
|
||||||
|
// segment 2
|
||||||
|
test_option_combinations3<quote>();
|
||||||
|
test_option_combinations3<escape, quote>();
|
||||||
|
|
||||||
|
// segment 3
|
||||||
|
test_option_combinations3<escape, multiline>();
|
||||||
|
test_option_combinations3<quote, multiline>();
|
||||||
|
|
||||||
|
// segment 4
|
||||||
|
test_option_combinations3<escape, quote, multiline>();
|
||||||
|
test_option_combinations3<escape, quote, multiline_r>();
|
||||||
|
|
||||||
|
// segment 5
|
||||||
|
test_option_combinations<escape, quote, multiline, triml>();
|
||||||
|
test_option_combinations<escape, quote, multiline, trimr>();
|
||||||
|
|
||||||
|
// segment 6
|
||||||
|
test_option_combinations3<escape, quote, multiline>();
|
||||||
|
test_option_combinations3<escape, quote, multiline, trim>();
|
||||||
|
#else
|
||||||
|
|
||||||
|
test_option_combinations3<escape, quote, multiline>();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
11
test/test_parser2_1.cpp
Normal file
11
test/test_parser2_1.cpp
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#define SEGMENT_NAME "segment1"
|
||||||
|
#include "test_parser2.hpp"
|
||||||
|
|
||||||
|
TEST_CASE("parser test various cases version 2 segment 1") {
|
||||||
|
#ifdef CMAKE_GITHUB_CI
|
||||||
|
using escape = ss::escape<'\\'>;
|
||||||
|
|
||||||
|
test_option_combinations3<>();
|
||||||
|
test_option_combinations3<escape>();
|
||||||
|
#endif
|
||||||
|
}
|
12
test/test_parser2_2.cpp
Normal file
12
test/test_parser2_2.cpp
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#define SEGMENT_NAME "segment2"
|
||||||
|
#include "test_parser2.hpp"
|
||||||
|
|
||||||
|
TEST_CASE("parser test various cases version 2 segment 2") {
|
||||||
|
#ifdef CMAKE_GITHUB_CI
|
||||||
|
using quote = ss::quote<'"'>;
|
||||||
|
using escape = ss::escape<'\\'>;
|
||||||
|
|
||||||
|
test_option_combinations3<quote>();
|
||||||
|
test_option_combinations3<escape, quote>();
|
||||||
|
#endif
|
||||||
|
}
|
13
test/test_parser2_3.cpp
Normal file
13
test/test_parser2_3.cpp
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#define SEGMENT_NAME "segment3"
|
||||||
|
#include "test_parser2.hpp"
|
||||||
|
|
||||||
|
TEST_CASE("parser test various cases version 2 segment 3") {
|
||||||
|
#ifdef CMAKE_GITHUB_CI
|
||||||
|
using quote = ss::quote<'"'>;
|
||||||
|
using escape = ss::escape<'\\'>;
|
||||||
|
using multiline = ss::multiline;
|
||||||
|
|
||||||
|
test_option_combinations3<escape, multiline>();
|
||||||
|
test_option_combinations3<quote, multiline>();
|
||||||
|
#endif
|
||||||
|
}
|
14
test/test_parser2_4.cpp
Normal file
14
test/test_parser2_4.cpp
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#define SEGMENT_NAME "segment4"
|
||||||
|
#include "test_parser2.hpp"
|
||||||
|
|
||||||
|
TEST_CASE("parser test various cases version 2 segment 4") {
|
||||||
|
#ifdef CMAKE_GITHUB_CI
|
||||||
|
using quote = ss::quote<'"'>;
|
||||||
|
using escape = ss::escape<'\\'>;
|
||||||
|
using multiline = ss::multiline;
|
||||||
|
using multiline_r = ss::multiline_restricted<10>;
|
||||||
|
|
||||||
|
test_option_combinations3<escape, quote, multiline>();
|
||||||
|
test_option_combinations3<escape, quote, multiline_r>();
|
||||||
|
#endif
|
||||||
|
}
|
15
test/test_parser2_5.cpp
Normal file
15
test/test_parser2_5.cpp
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
#define SEGMENT_NAME "segment5"
|
||||||
|
#include "test_parser2.hpp"
|
||||||
|
|
||||||
|
TEST_CASE("parser test various cases version 2 segment 5") {
|
||||||
|
#ifdef CMAKE_GITHUB_CI
|
||||||
|
using quote = ss::quote<'"'>;
|
||||||
|
using escape = ss::escape<'\\'>;
|
||||||
|
using multiline = ss::multiline;
|
||||||
|
using trimr = ss::trim_right<' '>;
|
||||||
|
using triml = ss::trim_left<' '>;
|
||||||
|
|
||||||
|
test_option_combinations<escape, quote, multiline, triml>();
|
||||||
|
test_option_combinations<escape, quote, multiline, trimr>();
|
||||||
|
#endif
|
||||||
|
}
|
10
test/test_parser2_6.cpp
Normal file
10
test/test_parser2_6.cpp
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#define SEGMENT_NAME "segment6"
|
||||||
|
#include "test_parser2.hpp"
|
||||||
|
|
||||||
|
TEST_CASE("parser test various cases version 2 segment 6") {
|
||||||
|
using quote = ss::quote<'"'>;
|
||||||
|
using escape = ss::escape<'\\'>;
|
||||||
|
using multiline = ss::multiline;
|
||||||
|
|
||||||
|
test_option_combinations3<escape, quote, multiline>();
|
||||||
|
}
|
@ -3,12 +3,14 @@
|
|||||||
set -x
|
set -x
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
python3 script/single_header_generator.py > ssp.cpp
|
TMP_HDR=test_single_header.hpp
|
||||||
|
TMP_SRC=test_single_header.cpp
|
||||||
|
TMP_BIN=test_single_header
|
||||||
|
|
||||||
echo 'int main(){ ss::parser p{""}; p.get_next<int, float>(); return 0; }' \
|
python3 script/single_header_generator.py > ${TMP_HDR}
|
||||||
>> ssp.cpp
|
cat ${TMP_HDR} test/test_single_header_main.txt > ${TMP_SRC}
|
||||||
|
|
||||||
g++ -std=c++17 ssp.cpp -o ssp.bin -Wall -Wextra
|
g++ -std=c++17 ${TMP_SRC} -o ${TMP_BIN} -Wall -Wextra
|
||||||
./ssp.bin
|
./${TMP_BIN}
|
||||||
|
|
||||||
rm ssp.cpp ssp.bin
|
rm ${TMP_HDR} ${TMP_SRC} ${TMP_BIN}
|
||||||
|
12
test/test_single_header_main.txt
Normal file
12
test/test_single_header_main.txt
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
int main() {
|
||||||
|
using quote = ss::quote<'"'>;
|
||||||
|
using escape = ss::escape<'\\'>;
|
||||||
|
using trim = ss::trim<' '>;
|
||||||
|
|
||||||
|
std::string data = "1,string,2.34,c";
|
||||||
|
|
||||||
|
ss::parser<quote, escape, trim, ss::multiline> p{data.c_str(), data.size()};
|
||||||
|
auto tup = p.get_next<int, std::string, float, std::optional<char>>();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
@ -5,15 +5,15 @@
|
|||||||
#include <ss/splitter.hpp>
|
#include <ss/splitter.hpp>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
constexpr static auto combinations_size_default = 4;
|
constexpr static auto num_combinations_default = 4;
|
||||||
size_t combinations_size = combinations_size_default;
|
size_t num_combinations = num_combinations_default;
|
||||||
|
|
||||||
struct set_combinations_size {
|
struct set_num_combinations {
|
||||||
set_combinations_size(size_t size) {
|
set_num_combinations(size_t size) {
|
||||||
combinations_size = size;
|
num_combinations = size;
|
||||||
}
|
}
|
||||||
~set_combinations_size() {
|
~set_num_combinations() {
|
||||||
combinations_size = combinations_size_default;
|
num_combinations = num_combinations_default;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -127,33 +127,13 @@ std::vector<std::string> combinations(const std::vector<std::string>& v,
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::vector<std::string>> vector_combinations(
|
|
||||||
const std::vector<std::string>& v, size_t n) {
|
|
||||||
std::vector<std::vector<std::string>> ret;
|
|
||||||
if (n <= 1) {
|
|
||||||
for (const auto& i : v) {
|
|
||||||
ret.push_back({i});
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto inner_combinations = vector_combinations(v, n - 1);
|
|
||||||
for (const auto& i : v) {
|
|
||||||
for (auto j : inner_combinations) {
|
|
||||||
j.insert(j.begin(), i);
|
|
||||||
ret.push_back(move(j));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::pair<std::vector<std::string>, std::vector<std::vector<std::string>>>
|
std::pair<std::vector<std::string>, std::vector<std::vector<std::string>>>
|
||||||
make_combinations(const std::vector<std::string>& input,
|
make_combinations(const std::vector<std::string>& input,
|
||||||
const std::vector<std::string>& output,
|
const std::vector<std::string>& output,
|
||||||
const std::string& delim) {
|
const std::string& delim) {
|
||||||
std::vector<std::string> lines;
|
std::vector<std::string> lines;
|
||||||
std::vector<std::vector<std::string>> expectations;
|
std::vector<std::vector<std::string>> expectations;
|
||||||
for (size_t i = 0; i < combinations_size; ++i) {
|
for (size_t i = 0; i < num_combinations; ++i) {
|
||||||
auto l = combinations(input, delim, i);
|
auto l = combinations(input, delim, i);
|
||||||
lines.reserve(lines.size() + l.size());
|
lines.reserve(lines.size() + l.size());
|
||||||
lines.insert(lines.end(), l.begin(), l.end());
|
lines.insert(lines.end(), l.begin(), l.end());
|
||||||
@ -165,7 +145,7 @@ make_combinations(const std::vector<std::string>& input,
|
|||||||
|
|
||||||
return {std::move(lines), std::move(expectations)};
|
return {std::move(lines), std::move(expectations)};
|
||||||
}
|
}
|
||||||
} /* namespace */
|
} /* anonymous namespace */
|
||||||
|
|
||||||
/* ********************************** */
|
/* ********************************** */
|
||||||
/* ********************************** */
|
/* ********************************** */
|
||||||
@ -173,9 +153,12 @@ make_combinations(const std::vector<std::string>& input,
|
|||||||
using matches_type = std::vector<std::pair<case_type, std::string>>;
|
using matches_type = std::vector<std::pair<case_type, std::string>>;
|
||||||
|
|
||||||
template <typename... Matchers>
|
template <typename... Matchers>
|
||||||
void test_combinations(matches_type& matches, std::vector<std::string> delims) {
|
static inline void test_combinations(matches_type& matches,
|
||||||
|
std::vector<std::string> delims) {
|
||||||
|
|
||||||
ss::splitter<Matchers...> s;
|
ss::splitter<Matchers...> s;
|
||||||
|
ss::splitter<Matchers..., ss::throw_on_error> st;
|
||||||
|
|
||||||
std::vector<std::string> inputs;
|
std::vector<std::string> inputs;
|
||||||
std::vector<std::string> outputs;
|
std::vector<std::string> outputs;
|
||||||
for (const auto& [cases, e] : matches) {
|
for (const auto& [cases, e] : matches) {
|
||||||
@ -194,6 +177,13 @@ void test_combinations(matches_type& matches, std::vector<std::string> delims) {
|
|||||||
auto vec = s.split(buff(lines[i].c_str()), delim);
|
auto vec = s.split(buff(lines[i].c_str()), delim);
|
||||||
CHECK(s.valid());
|
CHECK(s.valid());
|
||||||
CHECK_EQ(words(vec), expectations[i]);
|
CHECK_EQ(words(vec), expectations[i]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
auto vec = st.split(buff(lines[i].c_str()), delim);
|
||||||
|
CHECK_EQ(words(vec), expectations[i]);
|
||||||
|
} catch (ss::exception& e) {
|
||||||
|
FAIL(std::string{e.what()});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -253,7 +243,7 @@ TEST_CASE("splitter test with quote") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("splitter test with trim") {
|
TEST_CASE("splitter test with trim") {
|
||||||
auto guard = set_combinations_size(3);
|
auto guard = set_num_combinations(3);
|
||||||
case_type case1 = spaced({R"(x)"}, " ");
|
case_type case1 = spaced({R"(x)"}, " ");
|
||||||
case_type case2 = spaced({R"(yy)"}, " ");
|
case_type case2 = spaced({R"(yy)"}, " ");
|
||||||
case_type case3 = spaced({R"(y y)"}, " ");
|
case_type case3 = spaced({R"(y y)"}, " ");
|
||||||
@ -320,7 +310,7 @@ TEST_CASE("splitter test with escape") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("splitter test with quote and trim") {
|
TEST_CASE("splitter test with quote and trim") {
|
||||||
auto guard = set_combinations_size(3);
|
auto guard = set_num_combinations(3);
|
||||||
case_type case1 = spaced({R"("""")"}, " ");
|
case_type case1 = spaced({R"("""")"}, " ");
|
||||||
case_type case2 = spaced({R"("x""x")", R"(x"x)"}, " ");
|
case_type case2 = spaced({R"("x""x")", R"(x"x)"}, " ");
|
||||||
case_type case3 = spaced({R"("")", R"()"}, " ");
|
case_type case3 = spaced({R"("")", R"()"}, " ");
|
||||||
@ -438,7 +428,7 @@ TEST_CASE("splitter test with escape and trim") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("splitter test with quote and escape and trim") {
|
TEST_CASE("splitter test with quote and escape and trim") {
|
||||||
auto guard = set_combinations_size(3);
|
auto guard = set_num_combinations(3);
|
||||||
case_type case1 = spaced({R"("\"")", R"(\")", R"("""")"}, " ");
|
case_type case1 = spaced({R"("\"")", R"(\")", R"("""")"}, " ");
|
||||||
case_type case2 =
|
case_type case2 =
|
||||||
spaced({R"("x\"x")", R"(x\"x)", R"(x"x)", R"("x""x")"}, " ");
|
spaced({R"("x\"x")", R"(x\"x)", R"(x"x)", R"("x""x")"}, " ");
|
||||||
@ -509,6 +499,15 @@ TEST_CASE("splitter test error mode") {
|
|||||||
CHECK_FALSE(s.valid());
|
CHECK_FALSE(s.valid());
|
||||||
CHECK_FALSE(s.unterminated_quote());
|
CHECK_FALSE(s.unterminated_quote());
|
||||||
CHECK_FALSE(s.error_msg().empty());
|
CHECK_FALSE(s.error_msg().empty());
|
||||||
|
|
||||||
|
try {
|
||||||
|
ss::splitter<ss::throw_on_error> s;
|
||||||
|
s.split(buff("just,some,strings"), "");
|
||||||
|
FAIL("expected exception");
|
||||||
|
} catch (ss::exception& e) {
|
||||||
|
CHECK_FALSE(std::string{e.what()}.empty());
|
||||||
|
CHECK_FALSE(s.unterminated_quote());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -522,11 +521,17 @@ TEST_CASE("splitter test error mode") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename Splitter>
|
template <typename Splitter>
|
||||||
auto expect_unterminated_quote(Splitter& s, const std::string& line) {
|
static inline auto expect_unterminated_quote(Splitter& s,
|
||||||
auto vec = s.split(buff(line.c_str()));
|
const std::string& line) {
|
||||||
CHECK_FALSE(s.valid());
|
try {
|
||||||
CHECK(s.unterminated_quote());
|
auto vec = s.split(buff(line.c_str()));
|
||||||
return vec;
|
CHECK(s.valid());
|
||||||
|
CHECK(s.unterminated_quote());
|
||||||
|
return vec;
|
||||||
|
} catch (ss::exception& e) {
|
||||||
|
FAIL(std::string{e.what()});
|
||||||
|
return decltype(s.split(buff(line.c_str()))){};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace ss {
|
namespace ss {
|
||||||
@ -543,14 +548,16 @@ public:
|
|||||||
return splitter.size_shifted();
|
return splitter.size_shifted();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} /* ss */
|
} /* namespace ss */
|
||||||
|
|
||||||
TEST_CASE("splitter test resplit unterminated quote") {
|
TEST_CASE("splitter test resplit unterminated quote") {
|
||||||
|
|
||||||
{
|
{
|
||||||
ss::converter<ss::quote<'"'>, ss::multiline, ss::escape<'\\'>> c;
|
ss::converter<ss::quote<'"'>, ss::multiline, ss::escape<'\\'>> c;
|
||||||
auto& s = c.splitter;
|
auto& s = c.splitter;
|
||||||
|
|
||||||
auto vec = expect_unterminated_quote(s, R"("x)");
|
auto vec = expect_unterminated_quote(s, R"("x)");
|
||||||
|
|
||||||
CHECK_EQ(vec.size(), 1);
|
CHECK_EQ(vec.size(), 1);
|
||||||
REQUIRE(s.unterminated_quote());
|
REQUIRE(s.unterminated_quote());
|
||||||
|
|
||||||
@ -631,7 +638,7 @@ TEST_CASE("splitter test resplit unterminated quote") {
|
|||||||
{
|
{
|
||||||
auto new_line = buff.append(R"(,dom)");
|
auto new_line = buff.append(R"(,dom)");
|
||||||
vec = c.resplit(new_line, strlen(new_line));
|
vec = c.resplit(new_line, strlen(new_line));
|
||||||
CHECK_FALSE(s.valid());
|
CHECK(s.valid());
|
||||||
CHECK(s.unterminated_quote());
|
CHECK(s.unterminated_quote());
|
||||||
CHECK_EQ(words(vec), expected);
|
CHECK_EQ(words(vec), expected);
|
||||||
}
|
}
|
||||||
@ -768,54 +775,374 @@ TEST_CASE("splitter test resplit unterminated quote") {
|
|||||||
CHECK_EQ(words(vec), expected);
|
CHECK_EQ(words(vec), expected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
ss::converter<ss::quote<'"'>, ss::escape<'\\'>, ss::multiline> c;
|
||||||
|
auto& s = c.splitter;
|
||||||
|
auto vec = expect_unterminated_quote(s, R"("just\"some","ra)");
|
||||||
|
std::vector<std::string> expected{"just\"some"};
|
||||||
|
auto w = words(vec);
|
||||||
|
w.pop_back();
|
||||||
|
CHECK_EQ(w, expected);
|
||||||
|
REQUIRE(s.unterminated_quote());
|
||||||
|
{
|
||||||
|
auto new_line = buff.append(R"(n,dom",str\"ings)");
|
||||||
|
// invalid resplit size
|
||||||
|
vec = c.resplit(new_line, 4);
|
||||||
|
CHECK(!s.valid());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("splitter test invalid splits") {
|
TEST_CASE("splitter test resplit unterminated quote with exceptions") {
|
||||||
ss::converter<ss::string_error, ss::quote<'"'>, ss::trim<' '>,
|
|
||||||
ss::escape<'\\'>>
|
try {
|
||||||
c;
|
ss::converter<ss::quote<'"'>, ss::multiline, ss::escape<'\\'>,
|
||||||
|
ss::throw_on_error>
|
||||||
|
c;
|
||||||
|
auto& s = c.splitter;
|
||||||
|
|
||||||
|
auto vec = expect_unterminated_quote(s, R"("x)");
|
||||||
|
|
||||||
|
CHECK_EQ(vec.size(), 1);
|
||||||
|
REQUIRE(s.unterminated_quote());
|
||||||
|
|
||||||
|
{
|
||||||
|
auto new_linet =
|
||||||
|
buff.append_overwrite_last(R"(a\x)", c.size_shifted());
|
||||||
|
|
||||||
|
vec = c.resplit(new_linet, strlen(new_linet));
|
||||||
|
|
||||||
|
CHECK(s.unterminated_quote());
|
||||||
|
CHECK_EQ(vec.size(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto new_linet =
|
||||||
|
buff.append_overwrite_last(R"(")", c.size_shifted());
|
||||||
|
|
||||||
|
vec = c.resplit(new_linet, strlen(new_linet));
|
||||||
|
REQUIRE(s.valid());
|
||||||
|
CHECK_FALSE(s.unterminated_quote());
|
||||||
|
REQUIRE_EQ(vec.size(), 1);
|
||||||
|
CHECK_EQ(words(vec)[0], "xax");
|
||||||
|
}
|
||||||
|
} catch (ss::exception& e) {
|
||||||
|
FAIL(std::string{e.what()});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
ss::converter<ss::quote<'"'>, ss::multiline, ss::throw_on_error> c;
|
||||||
|
auto& s = c.splitter;
|
||||||
|
auto vec = expect_unterminated_quote(s, "\"just");
|
||||||
|
CHECK_EQ(vec.size(), 1);
|
||||||
|
|
||||||
|
auto new_line = buff.append(R"(",strings)");
|
||||||
|
vec = c.resplit(new_line, strlen(new_line));
|
||||||
|
CHECK(s.valid());
|
||||||
|
CHECK_FALSE(s.unterminated_quote());
|
||||||
|
std::vector<std::string> expected{"just", "strings"};
|
||||||
|
CHECK_EQ(words(vec), expected);
|
||||||
|
} catch (ss::exception& e) {
|
||||||
|
FAIL(std::string{e.what()});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
ss::converter<ss::quote<'"'>, ss::multiline, ss::throw_on_error> c;
|
||||||
|
auto& s = c.splitter;
|
||||||
|
auto vec = expect_unterminated_quote(s, "just,some,\"random");
|
||||||
|
std::vector<std::string> expected{"just", "some", "just,some,\""};
|
||||||
|
CHECK_EQ(words(vec), expected);
|
||||||
|
|
||||||
|
auto new_line = buff.append(R"(",strings)");
|
||||||
|
vec = c.resplit(new_line, strlen(new_line));
|
||||||
|
CHECK(s.valid());
|
||||||
|
CHECK_FALSE(s.unterminated_quote());
|
||||||
|
expected = {"just", "some", "random", "strings"};
|
||||||
|
CHECK_EQ(words(vec), expected);
|
||||||
|
} catch (ss::exception& e) {
|
||||||
|
FAIL(std::string{e.what()});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
ss::converter<ss::quote<'"'>, ss::multiline, ss::throw_on_error> c;
|
||||||
|
auto& s = c.splitter;
|
||||||
|
auto vec = expect_unterminated_quote(s, R"("just","some","ran"")");
|
||||||
|
std::vector<std::string> expected{"just", "some", R"("just","some",")"};
|
||||||
|
CHECK_EQ(words(vec), expected);
|
||||||
|
|
||||||
|
auto new_line =
|
||||||
|
buff.append_overwrite_last(R"(,dom","strings")", c.size_shifted());
|
||||||
|
vec = c.resplit(new_line, strlen(new_line));
|
||||||
|
CHECK(s.valid());
|
||||||
|
CHECK_FALSE(s.unterminated_quote());
|
||||||
|
expected = {"just", "some", "ran\",dom", "strings"};
|
||||||
|
CHECK_EQ(words(vec), expected);
|
||||||
|
} catch (ss::exception& e) {
|
||||||
|
FAIL(std::string{e.what()});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
ss::converter<ss::quote<'"'>, ss::multiline, ss::throw_on_error> c;
|
||||||
|
auto& s = c.splitter;
|
||||||
|
auto vec = expect_unterminated_quote(s, R"("just","some","ran)");
|
||||||
|
std::vector<std::string> expected{"just", "some", R"("just","some",")"};
|
||||||
|
CHECK_EQ(words(vec), expected);
|
||||||
|
REQUIRE(s.unterminated_quote());
|
||||||
|
|
||||||
|
{
|
||||||
|
auto new_line = buff.append(R"(,dom)");
|
||||||
|
vec = c.resplit(new_line, strlen(new_line));
|
||||||
|
CHECK(s.valid());
|
||||||
|
CHECK(s.unterminated_quote());
|
||||||
|
CHECK_EQ(words(vec), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto new_line = buff.append(R"(",strings)");
|
||||||
|
vec = c.resplit(new_line, strlen(new_line));
|
||||||
|
CHECK(s.valid());
|
||||||
|
CHECK_FALSE(s.unterminated_quote());
|
||||||
|
expected = {"just", "some", "ran,dom", "strings"};
|
||||||
|
CHECK_EQ(words(vec), expected);
|
||||||
|
}
|
||||||
|
} catch (ss::exception& e) {
|
||||||
|
FAIL(std::string{e.what()});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
ss::converter<ss::quote<'"'>, ss::escape<'\\'>, ss::multiline,
|
||||||
|
ss::throw_on_error>
|
||||||
|
c;
|
||||||
|
auto& s = c.splitter;
|
||||||
|
auto vec = expect_unterminated_quote(s, R"("just\"some","ra)");
|
||||||
|
std::vector<std::string> expected{"just\"some"};
|
||||||
|
auto w = words(vec);
|
||||||
|
w.pop_back();
|
||||||
|
CHECK_EQ(w, expected);
|
||||||
|
REQUIRE(s.unterminated_quote());
|
||||||
|
{
|
||||||
|
auto new_line = buff.append(R"(n,dom",str\"ings)");
|
||||||
|
vec = c.resplit(new_line, strlen(new_line));
|
||||||
|
CHECK(s.valid());
|
||||||
|
CHECK_FALSE(s.unterminated_quote());
|
||||||
|
expected = {"just\"some", "ran,dom", "str\"ings"};
|
||||||
|
CHECK_EQ(words(vec), expected);
|
||||||
|
}
|
||||||
|
} catch (ss::exception& e) {
|
||||||
|
FAIL(std::string{e.what()});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
ss::converter<ss::quote<'"'>, ss::escape<'\\'>, ss::multiline,
|
||||||
|
ss::throw_on_error>
|
||||||
|
c;
|
||||||
|
auto& s = c.splitter;
|
||||||
|
auto vec =
|
||||||
|
expect_unterminated_quote(s, "3,4,"
|
||||||
|
"\"just0\\\n1\\\n22\\\n33333x\\\n4");
|
||||||
|
|
||||||
|
std::vector<std::string> expected{"3", "4"};
|
||||||
|
auto w = words(vec);
|
||||||
|
w.pop_back();
|
||||||
|
CHECK_EQ(w, expected);
|
||||||
|
REQUIRE(s.unterminated_quote());
|
||||||
|
{
|
||||||
|
auto new_line =
|
||||||
|
buff.append_overwrite_last("\nx5strings\"", c.size_shifted());
|
||||||
|
vec = c.resplit(new_line, strlen(new_line));
|
||||||
|
CHECK(s.valid());
|
||||||
|
CHECK_FALSE(s.unterminated_quote());
|
||||||
|
expected = {"3", "4", "just0\n1\n22\n33333x\n4\nx5strings"};
|
||||||
|
CHECK_EQ(words(vec), expected);
|
||||||
|
}
|
||||||
|
} catch (ss::exception& e) {
|
||||||
|
FAIL(std::string{e.what()});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
ss::converter<ss::quote<'"'>, ss::escape<'\\'>, ss::multiline,
|
||||||
|
ss::throw_on_error>
|
||||||
|
c;
|
||||||
|
auto& s = c.splitter;
|
||||||
|
auto vec = expect_unterminated_quote(s, R"("just\"some","ra"")");
|
||||||
|
std::vector<std::string> expected{"just\"some"};
|
||||||
|
auto w = words(vec);
|
||||||
|
w.pop_back();
|
||||||
|
CHECK_EQ(w, expected);
|
||||||
|
REQUIRE(s.unterminated_quote());
|
||||||
|
{
|
||||||
|
auto new_line = buff.append_overwrite_last(R"(n,dom",str\"ings)",
|
||||||
|
c.size_shifted());
|
||||||
|
vec = c.resplit(new_line, strlen(new_line));
|
||||||
|
CHECK(s.valid());
|
||||||
|
CHECK_FALSE(s.unterminated_quote());
|
||||||
|
expected = {"just\"some", "ra\"n,dom", "str\"ings"};
|
||||||
|
CHECK_EQ(words(vec), expected);
|
||||||
|
}
|
||||||
|
} catch (ss::exception& e) {
|
||||||
|
FAIL(std::string{e.what()});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
ss::converter<ss::quote<'"'>, ss::escape<'\\'>, ss::multiline,
|
||||||
|
ss::throw_on_error>
|
||||||
|
c;
|
||||||
|
auto& s = c.splitter;
|
||||||
|
auto vec = expect_unterminated_quote(s, R"("just\"some","r\a\a\\\a\")");
|
||||||
|
std::vector<std::string> expected{"just\"some"};
|
||||||
|
auto w = words(vec);
|
||||||
|
w.pop_back();
|
||||||
|
CHECK_EQ(w, expected);
|
||||||
|
REQUIRE(s.unterminated_quote());
|
||||||
|
{
|
||||||
|
auto new_line = buff.append_overwrite_last(R"(n,dom",str\"ings)",
|
||||||
|
c.size_shifted());
|
||||||
|
vec = c.resplit(new_line, strlen(new_line));
|
||||||
|
CHECK(s.valid());
|
||||||
|
CHECK_FALSE(s.unterminated_quote());
|
||||||
|
expected = {"just\"some", "raa\\a\"n,dom", "str\"ings"};
|
||||||
|
CHECK_EQ(words(vec), expected);
|
||||||
|
}
|
||||||
|
} catch (ss::exception& e) {
|
||||||
|
FAIL(std::string{e.what()});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
ss::converter<ss::quote<'"'>, ss::trim<' '>, ss::multiline,
|
||||||
|
ss::throw_on_error>
|
||||||
|
c;
|
||||||
|
auto& s = c.splitter;
|
||||||
|
auto vec = expect_unterminated_quote(s, R"( "just" ,some, "ra )");
|
||||||
|
std::vector<std::string> expected{"just", "some"};
|
||||||
|
auto w = words(vec);
|
||||||
|
w.pop_back();
|
||||||
|
CHECK_EQ(w, expected);
|
||||||
|
REQUIRE(s.unterminated_quote());
|
||||||
|
{
|
||||||
|
auto new_line = buff.append(R"( n,dom" , strings )");
|
||||||
|
vec = c.resplit(new_line, strlen(new_line));
|
||||||
|
CHECK(s.valid());
|
||||||
|
CHECK_FALSE(s.unterminated_quote());
|
||||||
|
expected = {"just", "some", "ra n,dom", "strings"};
|
||||||
|
CHECK_EQ(words(vec), expected);
|
||||||
|
}
|
||||||
|
} catch (ss::exception& e) {
|
||||||
|
FAIL(std::string{e.what()});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
ss::converter<ss::quote<'"'>, ss::trim<' '>, ss::escape<'\\'>,
|
||||||
|
ss::multiline>
|
||||||
|
c;
|
||||||
|
auto& s = c.splitter;
|
||||||
|
auto vec = expect_unterminated_quote(s, R"( "ju\"st" ,some, "ra \")");
|
||||||
|
std::vector<std::string> expected{"ju\"st", "some"};
|
||||||
|
auto w = words(vec);
|
||||||
|
w.pop_back();
|
||||||
|
CHECK_EQ(w, expected);
|
||||||
|
REQUIRE(s.unterminated_quote());
|
||||||
|
{
|
||||||
|
auto new_line =
|
||||||
|
buff.append_overwrite_last(R"( n,dom" , strings )",
|
||||||
|
c.size_shifted());
|
||||||
|
vec = c.resplit(new_line, strlen(new_line));
|
||||||
|
CHECK(s.valid());
|
||||||
|
CHECK_FALSE(s.unterminated_quote());
|
||||||
|
expected = {"ju\"st", "some", "ra \" n,dom", "strings"};
|
||||||
|
CHECK_EQ(words(vec), expected);
|
||||||
|
}
|
||||||
|
} catch (ss::exception& e) {
|
||||||
|
FAIL(std::string{e.what()});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... Ts>
|
||||||
|
void test_invalid_splits() {
|
||||||
|
ss::converter<ss::quote<'"'>, ss::trim<' '>, ss::escape<'\\'>, Ts...> c;
|
||||||
auto& s = c.splitter;
|
auto& s = c.splitter;
|
||||||
|
|
||||||
|
auto check_error_msg = [&] {
|
||||||
|
if constexpr (ss::setup<Ts...>::string_error) {
|
||||||
|
CHECK_FALSE(s.error_msg().empty());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// empty delimiter
|
// empty delimiter
|
||||||
s.split(buff("some,random,strings"), "");
|
s.split(buff("some,random,strings"), "");
|
||||||
CHECK_FALSE(s.valid());
|
CHECK_FALSE(s.valid());
|
||||||
CHECK_FALSE(s.unterminated_quote());
|
CHECK_FALSE(s.unterminated_quote());
|
||||||
CHECK_FALSE(s.error_msg().empty());
|
check_error_msg();
|
||||||
|
|
||||||
// mismatched delimiter
|
// mismatched delimiter
|
||||||
s.split(buff(R"(some,"random,"strings")"));
|
s.split(buff(R"(some,"random,"strings")"));
|
||||||
CHECK_FALSE(s.valid());
|
CHECK_FALSE(s.valid());
|
||||||
CHECK_FALSE(s.unterminated_quote());
|
CHECK_FALSE(s.unterminated_quote());
|
||||||
CHECK_FALSE(s.error_msg().empty());
|
check_error_msg();
|
||||||
|
|
||||||
// unterminated escape
|
// unterminated escape
|
||||||
s.split(buff(R"(some,random,strings\)"));
|
s.split(buff(R"(some,random,strings\)"));
|
||||||
CHECK_FALSE(s.valid());
|
CHECK_FALSE(s.valid());
|
||||||
CHECK_FALSE(s.unterminated_quote());
|
CHECK_FALSE(s.unterminated_quote());
|
||||||
CHECK_FALSE(s.error_msg().empty());
|
check_error_msg();
|
||||||
|
|
||||||
// unterminated escape
|
// unterminated escape
|
||||||
s.split(buff(R"(some,random,"strings\)"));
|
s.split(buff(R"(some,random,"strings\)"));
|
||||||
CHECK_FALSE(s.valid());
|
CHECK_FALSE(s.valid());
|
||||||
CHECK_FALSE(s.unterminated_quote());
|
CHECK_FALSE(s.unterminated_quote());
|
||||||
CHECK_FALSE(s.error_msg().empty());
|
check_error_msg();
|
||||||
|
|
||||||
// unterminated quote
|
// unterminated quote
|
||||||
s.split(buff("some,random,\"strings"));
|
s.split(buff("some,random,\"strings"));
|
||||||
CHECK_FALSE(s.valid());
|
CHECK_FALSE(s.valid());
|
||||||
CHECK(s.unterminated_quote());
|
CHECK(s.unterminated_quote());
|
||||||
CHECK_FALSE(s.error_msg().empty());
|
check_error_msg();
|
||||||
|
|
||||||
// invalid resplit
|
// invalid resplit
|
||||||
char new_line[] = "some";
|
char new_line[] = "some";
|
||||||
auto a = c.resplit(new_line, strlen(new_line));
|
c.resplit(new_line, strlen(new_line));
|
||||||
CHECK_FALSE(s.valid());
|
CHECK_FALSE(s.valid());
|
||||||
|
check_error_msg();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("splitter test invalid splits") {
|
||||||
|
test_invalid_splits();
|
||||||
|
test_invalid_splits<ss::string_error>();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("splitter test invalid splits with exceptions") {
|
||||||
|
ss::converter<ss::throw_on_error, ss::quote<'"'>, ss::trim<' '>,
|
||||||
|
ss::escape<'\\'>>
|
||||||
|
c;
|
||||||
|
auto& s = c.splitter;
|
||||||
|
|
||||||
|
// empty delimiter
|
||||||
|
REQUIRE_EXCEPTION(s.split(buff("some,random,strings"), ""));
|
||||||
CHECK_FALSE(s.unterminated_quote());
|
CHECK_FALSE(s.unterminated_quote());
|
||||||
CHECK_FALSE(s.error_msg().empty());
|
|
||||||
|
// mismatched delimiter
|
||||||
|
REQUIRE_EXCEPTION(s.split(buff(R"(some,"random,"strings")")));
|
||||||
|
CHECK_FALSE(s.unterminated_quote());
|
||||||
|
|
||||||
|
// unterminated escape
|
||||||
|
REQUIRE_EXCEPTION(s.split(buff(R"(some,random,strings\)")));
|
||||||
|
CHECK_FALSE(s.unterminated_quote());
|
||||||
|
|
||||||
|
// unterminated escape
|
||||||
|
REQUIRE_EXCEPTION(s.split(buff(R"(some,random,"strings\)")));
|
||||||
|
CHECK_FALSE(s.unterminated_quote());
|
||||||
|
|
||||||
|
// unterminated quote
|
||||||
|
REQUIRE_EXCEPTION(s.split(buff("some,random,\"strings")));
|
||||||
|
CHECK(s.unterminated_quote());
|
||||||
|
|
||||||
|
// invalid resplit
|
||||||
|
char new_line[] = "some";
|
||||||
|
REQUIRE_EXCEPTION(c.resplit(new_line, strlen(new_line)));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("splitter test with trim_left") {
|
TEST_CASE("splitter test with trim_left") {
|
||||||
auto guard = set_combinations_size(3);
|
auto guard = set_num_combinations(3);
|
||||||
case_type case1 = spaced_left({R"(x )"}, " ");
|
case_type case1 = spaced_left({R"(x )"}, " ");
|
||||||
case_type case2 = spaced_left({R"(yy )"}, " ");
|
case_type case2 = spaced_left({R"(yy )"}, " ");
|
||||||
case_type case3 = spaced_left({R"(y y )"}, " ");
|
case_type case3 = spaced_left({R"(y y )"}, " ");
|
||||||
@ -845,7 +1172,7 @@ TEST_CASE("splitter test with trim_left") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("splitter test with trim_right") {
|
TEST_CASE("splitter test with trim_right") {
|
||||||
auto guard = set_combinations_size(3);
|
auto guard = set_num_combinations(3);
|
||||||
case_type case1 = spaced_right({R"( x)"}, " ");
|
case_type case1 = spaced_right({R"( x)"}, " ");
|
||||||
case_type case2 = spaced_right({R"( yy)"}, " ");
|
case_type case2 = spaced_right({R"( yy)"}, " ");
|
||||||
case_type case3 = spaced_right({R"( y y)"}, " ");
|
case_type case3 = spaced_right({R"( y y)"}, " ");
|
||||||
@ -876,7 +1203,7 @@ TEST_CASE("splitter test with trim_right") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("splitter test with trim_right and trim_left") {
|
TEST_CASE("splitter test with trim_right and trim_left") {
|
||||||
auto guard = set_combinations_size(3);
|
auto guard = set_num_combinations(3);
|
||||||
case_type case1 = spaced_right({R"(-x)"}, "-");
|
case_type case1 = spaced_right({R"(-x)"}, "-");
|
||||||
case_type case2 = spaced_left({R"(yy_)"}, "_");
|
case_type case2 = spaced_left({R"(yy_)"}, "_");
|
||||||
case_type case3 = spaced_right({R"(-y y)"}, "-");
|
case_type case3 = spaced_right({R"(-y y)"}, "-");
|
||||||
@ -894,7 +1221,7 @@ TEST_CASE("splitter test with trim_right and trim_left") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("splitter test with quote and escape, trim_left and trim_right") {
|
TEST_CASE("splitter test with quote and escape, trim_left and trim_right") {
|
||||||
auto guard = set_combinations_size(3);
|
auto guard = set_num_combinations(3);
|
||||||
case_type case1 = spaced_left({R"("\"")", R"(\")", R"("""")"}, "_");
|
case_type case1 = spaced_left({R"("\"")", R"(\")", R"("""")"}, "_");
|
||||||
case_type case2 =
|
case_type case2 =
|
||||||
spaced_left({R"("x\"x")", R"(x\"x)", R"(x"x)", R"("x""x")"}, "_");
|
spaced_left({R"("x\"x")", R"(x\"x)", R"(x"x)", R"("x""x")"}, "_");
|
||||||
|
Loading…
Reference in New Issue
Block a user