mirror of
https://github.com/red0124/ssp.git
synced 2025-12-15 22:29:55 +01:00
Compare commits
81 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
57abdb3923 | ||
| 27ef7f2e21 | |||
| f58091cf02 | |||
|
|
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 | |||
|
|
304ca6ef0f | ||
| 103ff33f21 | |||
|
|
8b928de086 | ||
| 6edce88d79 | |||
|
|
6196f7796b | ||
| 5672aa635e | |||
| 3eefac93b1 | |||
| 774f452689 | |||
| 448066b173 | |||
|
|
a7eca6064a | ||
| a4ecbd4dc8 | |||
|
|
a9e9783e6a | ||
| 04edf1e532 | |||
|
|
8dcb69aa2c | ||
| db2a72c18b | |||
|
|
0e28a06799 | ||
| 2218b01b81 | |||
| f777b04eb8 | |||
| 7831bbd735 | |||
| 6efb39b2db | |||
| f2b49e6d6c | |||
| 5cb3c65b24 | |||
|
|
d58644fd67 | ||
| 56447eb1d6 | |||
| 86d732e743 | |||
| 031ab5f7fc | |||
| 420625b25c | |||
| 9fa9edb24d | |||
| a2d72cdef3 | |||
| c214975ca0 | |||
| d328f7d59d | |||
| 62055f03c7 | |||
| 999992e579 | |||
|
|
9944016e51 | ||
| c3dbe29dba | |||
| 7a418baa8d | |||
| 6620dc855f | |||
| 4f8fcae350 | |||
| 642750dc53 | |||
| bd8a134987 | |||
| 5cada3b45a | |||
| 0aacff5729 | |||
| 6d6caf7414 | |||
| ef57a20fff | |||
| 2c1fe9be9f | |||
| 8fb3f68be3 | |||
| c4f36d6bc0 | |||
| 27d0a2f33e | |||
| a8fa5c753c | |||
| 45d166f93d | |||
| 904432992e | |||
| 2944b90100 | |||
| 954d1922ab |
2
.github/workflows/ubuntu-latest-clang.yml
vendored
2
.github/workflows/ubuntu-latest-clang.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
|||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
version: [12, 11, 10, 9, 8, 7]
|
version: [11, 10, 9, 8, 7]
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
|||||||
63
.github/workflows/win-msvc.yml
vendored
Normal file
63
.github/workflows/win-msvc.yml
vendored
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
name: win-msvc-ci
|
||||||
|
|
||||||
|
on:
|
||||||
|
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
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.config.os }}
|
||||||
|
|
||||||
|
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]
|
||||||
|
|
||||||
|
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 -j ${{steps.cores.outputs.count}}
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -3,5 +3,8 @@ compile_commands.json
|
|||||||
.ccls-cache/*
|
.ccls-cache/*
|
||||||
experiment/
|
experiment/
|
||||||
build/
|
build/
|
||||||
|
hbuild/
|
||||||
subprojects/*
|
subprojects/*
|
||||||
!subprojects/*.wrap
|
!subprojects/*.wrap
|
||||||
|
ssp.cpp
|
||||||
|
ssp.bin
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ cmake_minimum_required(VERSION 3.14)
|
|||||||
|
|
||||||
project(
|
project(
|
||||||
ssp
|
ssp
|
||||||
VERSION 0.0.1
|
VERSION 1.4.0
|
||||||
DESCRIPTION "Static split parser"
|
DESCRIPTION "csv parser"
|
||||||
HOMEPAGE_URL "https://github.com/red0124/ssp"
|
HOMEPAGE_URL "https://github.com/red0124/ssp"
|
||||||
LANGUAGES CXX
|
LANGUAGES CXX
|
||||||
)
|
)
|
||||||
@@ -11,22 +11,23 @@ project(
|
|||||||
# ---- Warning guard ----
|
# ---- Warning guard ----
|
||||||
|
|
||||||
# Protect dependents from this project's warnings if the guard isn't disabled
|
# Protect dependents from this project's warnings if the guard isn't disabled
|
||||||
set(ssp_warning_guard SYSTEM)
|
set(SSP_WARNING_GUARD SYSTEM)
|
||||||
if(ssp_INCLUDE_WITHOUT_SYSTEM)
|
if(SSP_INCLUDE_WITHOUT_SYSTEM)
|
||||||
set(ssp_warning_guard "")
|
set(SSP_WARNING_GUARD "")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# ---- Dependencies ----
|
# ---- Dependencies ----
|
||||||
|
|
||||||
include(FetchContent)
|
include(FetchContent)
|
||||||
FetchContent_Declare(
|
fetchcontent_declare(
|
||||||
fast_float
|
fast_float
|
||||||
GIT_REPOSITORY https://github.com/red0124/fast_float.git
|
GIT_REPOSITORY https://github.com/red0124/fast_float.git
|
||||||
GIT_TAG origin/meson
|
GIT_TAG origin/meson
|
||||||
GIT_SHALLOW TRUE)
|
GIT_SHALLOW TRUE
|
||||||
|
)
|
||||||
|
|
||||||
FetchContent_MakeAvailable(fast_float)
|
fetchcontent_makeavailable(fast_float)
|
||||||
set(fast_float_source_dir "${FETCHCONTENT_BASE_DIR}/fast_float-src")
|
set(FAST_FLOAT_SOURCE_DIR "${FETCHCONTENT_BASE_DIR}/fast_float-src")
|
||||||
|
|
||||||
# ---- Declare library ----
|
# ---- Declare library ----
|
||||||
|
|
||||||
@@ -35,10 +36,10 @@ add_library(ssp::ssp ALIAS ssp)
|
|||||||
|
|
||||||
target_include_directories(
|
target_include_directories(
|
||||||
ssp
|
ssp
|
||||||
${ssp_warning_guard}
|
${SSP_WARNING_GUARD}
|
||||||
INTERFACE
|
INTERFACE
|
||||||
"$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>"
|
"$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>"
|
||||||
"$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/fast_float>"
|
"$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/fast_float>"
|
||||||
)
|
)
|
||||||
|
|
||||||
target_compile_features(ssp INTERFACE cxx_std_17)
|
target_compile_features(ssp INTERFACE cxx_std_17)
|
||||||
@@ -46,8 +47,8 @@ target_compile_features(ssp INTERFACE cxx_std_17)
|
|||||||
target_link_libraries(
|
target_link_libraries(
|
||||||
ssp
|
ssp
|
||||||
INTERFACE
|
INTERFACE
|
||||||
"$<$<AND:$<CXX_COMPILER_ID:AppleClang,Clang>,$<VERSION_LESS:$<CXX_COMPILER_VERSION>,9.0>>:c++fs>"
|
"$<$<AND:$<CXX_COMPILER_ID:AppleClang,Clang>,$<VERSION_LESS:$<CXX_COMPILER_VERSION>,9.0>>:c++fs>"
|
||||||
"$<$<AND:$<CXX_COMPILER_ID:GNU>,$<VERSION_LESS:$<CXX_COMPILER_VERSION>,9.1>>:stdc++fs>"
|
"$<$<AND:$<CXX_COMPILER_ID:GNU>,$<VERSION_LESS:$<CXX_COMPILER_VERSION>,9.1>>:stdc++fs>"
|
||||||
)
|
)
|
||||||
|
|
||||||
# ---- Install ----
|
# ---- Install ----
|
||||||
@@ -55,19 +56,21 @@ target_link_libraries(
|
|||||||
include(CMakePackageConfigHelpers)
|
include(CMakePackageConfigHelpers)
|
||||||
include(GNUInstallDirs)
|
include(GNUInstallDirs)
|
||||||
|
|
||||||
set(ssp_directory "ssp-${PROJECT_VERSION}")
|
set(SSP_DIRECTORY "ssp-${PROJECT_VERSION}")
|
||||||
set(ssp_include_directory "${CMAKE_INSTALL_INCLUDEDIR}")
|
set(SSP_INCLUDE_DIRECTORY "${CMAKE_INSTALL_INCLUDEDIR}")
|
||||||
|
|
||||||
install(
|
install(
|
||||||
DIRECTORY "${PROJECT_SOURCE_DIR}/include/" "${fast_float_source_dir}/include/"
|
DIRECTORY
|
||||||
DESTINATION "${ssp_include_directory}"
|
"${PROJECT_SOURCE_DIR}/include/"
|
||||||
|
"${FAST_FLOAT_SOURCE_DIR}/include/"
|
||||||
|
DESTINATION "${SSP_INCLUDE_DIRECTORY}"
|
||||||
COMPONENT ssp_Development
|
COMPONENT ssp_Development
|
||||||
)
|
)
|
||||||
|
|
||||||
install(
|
install(
|
||||||
TARGETS ssp
|
TARGETS ssp
|
||||||
EXPORT sspTargets
|
EXPORT sspTargets
|
||||||
INCLUDES DESTINATION "${ssp_include_directory}"
|
INCLUDES DESTINATION "${SSP_INCLUDE_DIRECTORY}"
|
||||||
)
|
)
|
||||||
|
|
||||||
write_basic_package_version_file(
|
write_basic_package_version_file(
|
||||||
@@ -76,11 +79,11 @@ write_basic_package_version_file(
|
|||||||
ARCH_INDEPENDENT
|
ARCH_INDEPENDENT
|
||||||
)
|
)
|
||||||
|
|
||||||
set(ssp_install_cmakedir "${CMAKE_INSTALL_LIBDIR}/cmake/${ssp_directory}")
|
set(SSP_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/${SSP_DIRECTORY}")
|
||||||
|
|
||||||
install(
|
install(
|
||||||
FILES "${PROJECT_BINARY_DIR}/ssp-config-version.cmake"
|
FILES "${PROJECT_BINARY_DIR}/ssp-config-version.cmake"
|
||||||
DESTINATION "${ssp_install_cmakedir}"
|
DESTINATION "${SSP_INSTALL_CMAKEDIR}"
|
||||||
COMPONENT ssp_Development
|
COMPONENT ssp_Development
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -88,10 +91,10 @@ install(
|
|||||||
EXPORT sspTargets
|
EXPORT sspTargets
|
||||||
FILE ssp-config.cmake
|
FILE ssp-config.cmake
|
||||||
NAMESPACE ssp::
|
NAMESPACE ssp::
|
||||||
DESTINATION "${ssp_install_cmakedir}"
|
DESTINATION "${SSP_INSTALL_CMAKEDIR}"
|
||||||
COMPONENT ssp_Development
|
COMPONENT ssp_Development
|
||||||
)
|
)
|
||||||
|
|
||||||
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
|
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
|
||||||
include(CPack)
|
include(CPack)
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
217
README.md
217
README.md
@@ -8,11 +8,12 @@
|
|||||||
```
|
```
|
||||||
|
|
||||||
[](https://opensource.org/licenses/MIT)
|
[](https://opensource.org/licenses/MIT)
|
||||||

|
[](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)
|
||||||
|
|
||||||
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 convert strings to specific types.](#The-converter)
|
||||||
|
|
||||||
@@ -34,9 +35,9 @@ 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 p{"students.csv", ","};
|
||||||
|
|
||||||
for(auto& [name, age, grade] : p.iterate<std::string, int, double>()) {
|
for(const auto& [name, age, grade] : p.iterate<std::string, int, float>()) {
|
||||||
if (p.valid()) {
|
if (p.valid()) {
|
||||||
std::cout << name << ' ' << age << ' ' << grade << std::endl;
|
std::cout << name << ' ' << age << ' ' << grade << std::endl;
|
||||||
}
|
}
|
||||||
@@ -53,21 +54,26 @@ Brian S. Wolfe 40 1.9
|
|||||||
Bill (Heath) Gates 65 3.3
|
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
|
* No exceptions
|
||||||
* [Works with quotes, escapes and spacings](#Setup)
|
* [Works with headers](#headers)
|
||||||
* [Works with values containing new lines](#Multiline)
|
* [Works with quotes, escapes and spacings](#setup)
|
||||||
* [Columns and rows can be ignored](#Special-types)
|
* [Works with values containing new lines](#multiline)
|
||||||
|
* [Columns and rows can be ignored](#special-types)
|
||||||
* Works with any type of delimiter
|
* Works with any type of 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)
|
* [Descriptive error handling can be enabled](#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`**
|
||||||
* [Conversions can be chained if invalid](#Substitute-conversions)
|
* [Conversions can be chained if invalid](#substitute-conversions)
|
||||||
* Fast
|
* Fast
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
@@ -77,48 +83,102 @@ $ 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
|
||||||
|
|
||||||
|
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.
|
||||||
|
```shell
|
||||||
|
$ cat students_with_header.csv
|
||||||
|
Name,Age,Grade
|
||||||
|
James Bailey,65,2.5
|
||||||
|
Brian S. Wolfe,40,1.9
|
||||||
|
Bill (Heath) Gates,65,3.3
|
||||||
|
```
|
||||||
|
```cpp
|
||||||
|
// ...
|
||||||
|
ss::parser p{"students.csv", ","};
|
||||||
|
p.use_fields("Name", "Grade");
|
||||||
|
|
||||||
|
for(const auto& [name, grade] : p.iterate<std::string, float>()) {
|
||||||
|
std::cout << name << ' ' << grade << std::endl;
|
||||||
|
}
|
||||||
|
// ...
|
||||||
|
```
|
||||||
|
```shell
|
||||||
|
$ ./a.out
|
||||||
|
James Bailey 2.5
|
||||||
|
Brian S. Wolfe 1.9
|
||||||
|
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.
|
||||||
|
```cpp
|
||||||
|
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.
|
||||||
|
```cpp
|
||||||
|
// ...
|
||||||
|
ss::parser p{"students.csv", ","};
|
||||||
|
p.use_fields("Name", "Grade");
|
||||||
|
|
||||||
|
const auto& [name, grade] = p.get_next<std::string, float>();
|
||||||
|
std::cout << name << ' ' << grade << std::endl;
|
||||||
|
|
||||||
|
if (p.field_exists("Age")) {
|
||||||
|
p.use_fields("Grade", "Name", "Age");
|
||||||
|
for (const auto& [grade, name, age] :
|
||||||
|
p.iterate<float, std::string, int>()) {
|
||||||
|
std::cout << grade << ' ' << name << ' ' << age << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ...
|
||||||
|
```
|
||||||
|
```shell
|
||||||
|
$ ./a.out
|
||||||
|
James Bailey 2.5
|
||||||
|
1.9 Brian S. Wolfe 40
|
||||||
|
3.3 Bill (Heath) Gates 65
|
||||||
|
```
|
||||||
## 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()) {
|
while(!p.eof()) {
|
||||||
auto [name, age, grade] = p.get_next<std::string, int, double>();
|
auto [name, age, grade] = p.get_next<std::string, int, float>();
|
||||||
if (p.valid()) {
|
if (p.valid()) {
|
||||||
std::cout << name << ' ' << age << ' ' << grade << std::endl;
|
std::cout << name << ' ' << 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 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 double 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, double>;
|
using student = std::tuple<std::string, int, float>;
|
||||||
|
|
||||||
// returns std::tuple<std::string, int, double>
|
// returns std::tuple<std::string, int, float>
|
||||||
auto [name, age, grade] = p.get_next<student>();
|
auto [name, 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 a student 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 name;
|
||||||
int age;
|
int age;
|
||||||
double grade;
|
float grade;
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
```cpp
|
```cpp
|
||||||
// returns student
|
// returns student
|
||||||
auto student = p.get_object<student, std::string, int, double>();
|
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 3 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,
|
||||||
@@ -126,21 +186,21 @@ auto vec = p.get_object<std::vector<std::string>, std::string, std::string,
|
|||||||
```
|
```
|
||||||
An iteration loop as in the first example which returns objects would look like:
|
An iteration loop as in the first example which returns objects would look like:
|
||||||
```cpp
|
```cpp
|
||||||
for(auto& student : p.iterate_object<student, std::string, int, double>()) {
|
for(const auto& student : 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 name;
|
||||||
int age;
|
int age;
|
||||||
double grade;
|
float grade;
|
||||||
|
|
||||||
auto tied() { return std::tie(name, age, grade); }
|
auto tied() { return std::tie(name, 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>();
|
||||||
@@ -164,11 +224,18 @@ using my_setup = ss::setup<ss::escape<'\\'>, ss::quote<'"'>>;
|
|||||||
// equivalent to p0 and p1
|
// equivalent to p0 and p1
|
||||||
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, each setup parameter defined comes with a slight performance loss, so use them only if needed.*
|
||||||
|
|
||||||
|
### Empty lines
|
||||||
|
Empty lines can be ignored by defining **`ss::ignore_empty`** within the setup parameters:
|
||||||
|
```cpp
|
||||||
|
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).
|
||||||
|
|
||||||
### 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:
|
||||||
```cpp
|
```cpp
|
||||||
ss::parser<ss::quote<'"'>> p{file_name};
|
ss::parser<ss::quote<'"'>> p{file_name};
|
||||||
```
|
```
|
||||||
@@ -181,7 +248,7 @@ Unterminated quotes result in an error (if multiline is not enabled).
|
|||||||
"James Bailey,65,2.5 -> error
|
"James Bailey,65,2.5 -> error
|
||||||
```
|
```
|
||||||
### Escaping
|
### Escaping
|
||||||
Escaping can be enabled by defining **ss::escape** within the setup parameters. Multiple character can be defined as escaping characters.It simply removes any special meaning of the character behind the escaped character, anything can be escaped. For example to use ``\`` as an escaping character:
|
Escaping can be enabled by defining **`ss::escape`** within the setup parameters. Multiple character can be defined as escaping characters.It simply removes any special meaning of the character behind the escaped character, anything can be escaped. For example to use ``\`` as an escaping character:
|
||||||
```cpp
|
```cpp
|
||||||
ss::parser<ss::escape<'\\'>> p{file_name};
|
ss::parser<ss::escape<'\\'>> p{file_name};
|
||||||
```
|
```
|
||||||
@@ -198,7 +265,7 @@ Its usage has more impact when used with quoting or spacing:
|
|||||||
"James \"Bailey\"" -> 'James "Bailey"'
|
"James \"Bailey\"" -> 'James "Bailey"'
|
||||||
```
|
```
|
||||||
### Spacing
|
### Spacing
|
||||||
Spacing can be enabled by defining **ss::trim** , **ss::trim_left** or **ss::trim_right** within the setup parameters. Multiple character can be defined as spacing characters, for example to use ``' '`` as an spacing character **ss::trim<' '>** needs to be defined. It removes any space from both sides of the row. To trim only the right side **ss::trim_right** can be used, and intuitively **ss::trim_left** to trim only the left side. If **ss::trim** is enabled, those lines would have an equivalent output:
|
Spacing can be enabled by defining **`ss::trim`** , **`ss::trim_left`** or **`ss::trim_right`** within the setup parameters. Multiple character can be defined as spacing characters, for example to use ``' '`` as an spacing character **`ss::trim<' '>`** needs to be defined. It removes any space from both sides of the row. To trim only the right side **`ss::trim_right`** can be used, and intuitively **`ss::trim_left`** to trim only the left side. If **`ss::trim`** is enabled, those lines would have an equivalent output:
|
||||||
```
|
```
|
||||||
James Bailey,65,2.5
|
James Bailey,65,2.5
|
||||||
James Bailey ,65,2.5
|
James Bailey ,65,2.5
|
||||||
@@ -214,7 +281,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};
|
||||||
@@ -241,7 +308,7 @@ ss::parser<ss::escape<'\\'>,
|
|||||||
ss::multiline_restricted<5>> p{file_name};
|
ss::multiline_restricted<5>> p{file_name};
|
||||||
|
|
||||||
while(!p.eof()) {
|
while(!p.eof()) {
|
||||||
auto [name, age, grade] = p.get_next<std::string, int, double>();
|
auto [name, age, grade] = p.get_next<std::string, int, float>();
|
||||||
if(!p.valid()) {
|
if(!p.valid()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -268,53 +335,59 @@ 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 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:
|
||||||
```cpp
|
```cpp
|
||||||
// returns std::tuple<std::string, double>
|
// returns std::tuple<std::string, float>
|
||||||
auto [name, grade] = p.get_next<std::string, void, double>();
|
auto [name, 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, double>;
|
using student = std::tuple<std::string, void, float>;
|
||||||
|
|
||||||
// returns std::tuple<std::string, double>
|
// returns std::tuple<std::string, float>
|
||||||
auto [name, grade] = p.get_next<student>();
|
auto [name, grade] = p.get_next<student>();
|
||||||
```
|
```
|
||||||
To ignore a whole row, **ignore_next** could be used, returns **false** if **eof**:
|
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
|
||||||
|
// string_view name stays valid until the next line is read
|
||||||
|
auto [name, age, grade] = p.get_next<std::string_view, int, float>();
|
||||||
|
```
|
||||||
|
|
||||||
|
To ignore a whole row, **`ignore_next`** could be used, returns **`false`** if **`eof`**:
|
||||||
```cpp
|
```cpp
|
||||||
bool parser::ignore_next();
|
bool parser::ignore_next();
|
||||||
```
|
```
|
||||||
**std::optional** could be passed if we wanted the conversion to proceed in the case of a failure returning **std::nullopt** for the specified column:
|
**`std::optional`** could be passed if we wanted the conversion to proceed in the case of a failure returning **`std::nullopt`** for the specified column:
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
// returns std::tuple<std::string, int, std::optional<double>>
|
// returns std::tuple<std::string, int, std::optional<float>>
|
||||||
auto [name, age, grade] = p.get_next<std::string, int, std::optional<double>();
|
auto [name, age, grade] = p.get_next<std::string, int, std::optional<float>();
|
||||||
if(grade) {
|
if(grade) {
|
||||||
// do something with grade
|
// do something with grade
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
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<double, char>>
|
// returns std::tuple<std::string, int, std::variant<float, char>>
|
||||||
auto [name, age, grade] =
|
auto [name, age, grade] =
|
||||||
p.get_next<std::string, int, std::variant<double, char>();
|
p.get_next<std::string, int, std::variant<float, char>();
|
||||||
if(std::holds_alternative<double>(grade)) {
|
if(std::holds_alternative<float>(grade)) {
|
||||||
// grade set as double
|
// 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
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
## 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 one 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 name 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, double>
|
// returns std::tuple<std::string, int, float>
|
||||||
auto [name, age, grade] =
|
auto [name, age, grade] =
|
||||||
p.get_next<ss::ne<std::string>, int, ss::ir<double, 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*):
|
||||||
```cpp
|
```cpp
|
||||||
// all ints exept 10 and 20
|
// all ints exept 10 and 20
|
||||||
ss::ax<int, 10, 20>
|
ss::ax<int, 10, 20>
|
||||||
@@ -323,7 +396,7 @@ ss::nx<int, 10, 20>
|
|||||||
// all values except the range [0, 10]
|
// all values except the range [0, 10]
|
||||||
ss::oor<int, 0, 10>
|
ss::oor<int, 0, 10>
|
||||||
```
|
```
|
||||||
To define a restriction, a class/struct needs to be made which has a **ss_valid** method which returns a **bool** and accepts one object. The type of the conversion will be the same as the type of the passed object within **ss_valid** and not the restriction itself. Optionally, an **error** method can be made to describe the invalid conversion.
|
To define a restriction, a class/struct needs to be made which has a **`ss_valid`** method which returns a **`bool`** and accepts one object. The type of the conversion will be the same as the type of the passed object within **`ss_valid`** and not the restriction itself. Optionally, an **`error`** method can be made to describe the invalid conversion.
|
||||||
```cpp
|
```cpp
|
||||||
template <typename T>
|
template <typename T>
|
||||||
struct even {
|
struct even {
|
||||||
@@ -344,7 +417,7 @@ auto [name, age] = p.get_next<std::string, even<int>, void>();
|
|||||||
```
|
```
|
||||||
## Custom conversions
|
## Custom conversions
|
||||||
|
|
||||||
Custom types can be used when converting values. A specialization of the **ss::extract** function needs to be made and you are good to go. A custom conversion for an enum would look like this:
|
Custom types can be used when converting values. A specialization of the **`ss::extract`** function needs to be made and you are good to go. A custom conversion for an enum would look like this:
|
||||||
```cpp
|
```cpp
|
||||||
enum class shape { circle, square, rectangle, triangle };
|
enum class shape { circle, square, rectangle, triangle };
|
||||||
|
|
||||||
@@ -363,11 +436,11 @@ inline bool ss::extract(const char* begin, const char* end, shape& dst) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
The shape enum will be used in an example below. The **inline** is there just to prevent multiple definition errors. The function returns **true** if the conversion was a success, and **false** otherwise. The function uses **const char*** begin and end for performance reasons.
|
The shape enum will be used in an example below. The **`inline`** is there just to prevent multiple definition errors. The function returns **`true`** if the conversion was a success, and **`false`** otherwise. The function uses **`const char*`** begin and end for performance reasons.
|
||||||
|
|
||||||
## Error handling
|
## Error handling
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
const std::string& parser::error_msg();
|
const std::string& parser::error_msg();
|
||||||
@@ -377,7 +450,7 @@ bool parser::eof();
|
|||||||
// ...
|
// ...
|
||||||
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.
|
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.
|
||||||
|
|
||||||
## Substitute conversions
|
## Substitute conversions
|
||||||
|
|
||||||
@@ -400,7 +473,6 @@ The delimiter is " ", and the number of columns varies depending on which shape
|
|||||||
```cpp
|
```cpp
|
||||||
ss::parser p{"shapes.txt", " "};
|
ss::parser p{"shapes.txt", " "};
|
||||||
if (!p.valid()) {
|
if (!p.valid()) {
|
||||||
std::cout << p.error_msg() << std::endl;
|
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -442,9 +514,9 @@ while (!p.eof()) {
|
|||||||
```
|
```
|
||||||
It is quite hard to make an error this way since most things will be checked at compile time.
|
It is quite hard to make an error this way since most things will be checked at compile time.
|
||||||
|
|
||||||
The **try_next** method works in a similar way as **get_next** but returns a **composit** which holds a **tuple** with an **optional** to the **tuple** returned by **get_next**. This **composite** has an **or_else** method (looks a bit like **tl::expected**) which is able to try additional conversions if the previous failed. **or_else** also returns a **composite**, but in its tuple is the **optional** to the **tuple** of the previous conversions and an **optional** to the **tuple** of the new conversion. (sounds more complicated than it is.
|
The **`try_next`** method works in a similar way as **`get_next`** but returns a **`composite`** which holds a **`tuple`** with an **`optional`** to the **`tuple`** returned by **`get_next`**. This **`composite`** has an **`or_else`** method (looks a bit like **`tl::expected`**) which is able to try additional conversions if the previous failed. **`or_else`** also returns a **`composite`**, but in its tuple is the **`optional`** to the **`tuple`** of the previous conversions and an **`optional`** to the **`tuple`** of the new conversion. (sounds more complicated than it is.
|
||||||
|
|
||||||
To fetch the **tuple** from the **composite** the **values** method is used. The value of the above used conversion would look something like this:
|
To fetch the **`tuple`** from the **`composite`** the **`values`** method is used. The value of the above used conversion would look something like this:
|
||||||
```cpp
|
```cpp
|
||||||
std::tuple<
|
std::tuple<
|
||||||
std::optional<std::tuple<shape, double>>,
|
std::optional<std::tuple<shape, double>>,
|
||||||
@@ -452,9 +524,9 @@ std::tuple<
|
|||||||
std::optional<std::tuple<shape, double, double, double>>
|
std::optional<std::tuple<shape, double, double, double>>
|
||||||
>
|
>
|
||||||
```
|
```
|
||||||
Similar to the way that **get_next** has a **get_object** alternative, **try_next** has a **try_object** alternative, and **or_else** has a **or_object** alternative. Also all rules applied to **get_next** also work with **try_next** , **or_else**, and all the other **composite** conversions.
|
Similar to the way that **`get_next`** has a **`get_object`** alternative, **`try_next`** has a **`try_object`** alternative, and **`or_else`** has a **`or_object`** alternative. Also all rules applied to **`get_next`** also work with **`try_next`** , **`or_else`**, and all the other **`composite`** conversions.
|
||||||
|
|
||||||
Each of those **composite** conversions can accept a lambda (or anything callable) as an argument and invoke it in case of a valid conversion. That lambda itself need not have any arguments, but if it does, it must either accept the whole **tuple**/object as one argument or all the elements of the tuple separately. If the lambda returns something that can be interpreted as **false** the conversion will fail, and the next conversion will try to apply. Rewriting the whole while loop using lambdas would look like this:
|
Each of those **`composite`** conversions can accept a lambda (or anything callable) as an argument and invoke it in case of a valid conversion. That lambda itself need not have any arguments, but if it does, it must either accept the whole **`tuple`**/object as one argument or all the elements of the tuple separately. If the lambda returns something that can be interpreted as **`false`** the conversion will fail, and the next conversion will try to apply. Rewriting the whole while loop using lambdas would look like this:
|
||||||
```cpp
|
```cpp
|
||||||
// non negative double
|
// non negative double
|
||||||
using udbl = ss::gte<double, 0>;
|
using udbl = ss::gte<double, 0>;
|
||||||
@@ -478,7 +550,7 @@ p.try_next<ss::nx<shape, shape::circle, shape::square>, udbl>(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
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 */ })
|
||||||
@@ -493,9 +565,9 @@ First of all, *type_traits.hpp* and *function_traits.hpp* contain many handy tra
|
|||||||
|
|
||||||
## The converter
|
## The converter
|
||||||
|
|
||||||
**ss::parser** is used to manipulate on files. It has a builtin file reader, but the conversions themselves are done using the **ss::converter**.
|
**`ss::parser`** is used to manipulate on files. It has a builtin file reader, but the conversions themselves are done using the **`ss::converter`**.
|
||||||
|
|
||||||
To convert a string the **convert** method can be used. It accepts a c-string as input and a delimiter, as **std::string**, and retruns a **tuple** of objects in the same way **get_next** does it for the parser. A whole object can be returned too using the **convert_object** method, again in an identical way **get_object** doest it for the parser.
|
To convert a string the **`convert`** method can be used. It accepts a c-string as input and a delimiter, as **`std::string`**, and retruns a **`tuple`** of objects in the same way **`get_next`** does it for the parser. A whole object can be returned too using the **`convert_object`** method, again in an identical way **`get_object`** doest it for the parser.
|
||||||
```cpp
|
```cpp
|
||||||
ss::converter c;
|
ss::converter c;
|
||||||
|
|
||||||
@@ -512,7 +584,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 pairs 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, ~~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.
|
||||||
```cpp
|
```cpp
|
||||||
ss::converter c;
|
ss::converter c;
|
||||||
auto split_line = c.split("circle 10", " ");
|
auto split_line = c.split("circle 10", " ");
|
||||||
@@ -525,7 +597,7 @@ std::string s;
|
|||||||
std::cin >> s;
|
std::cin >> s;
|
||||||
int num = c.convert<int>(s.c_str());
|
int num = c.convert<int>(s.c_str());
|
||||||
```
|
```
|
||||||
The same setup parameters also apply for the converter, tho multiline has not impact on it. Since escaping and quoting potentially modify the content of the given line, a converter which has those setup parameters defined does not have the same convert method, **the input line cannot be const**.
|
The same setup parameters also apply for the converter, tho multiline has not impact on it. Since escaping and quoting potentially modify the content of the given line, a converter which has those setup parameters defined does not have the same convert method, **`the input line cannot be const`**.
|
||||||
|
|
||||||
# Using as a project dependency
|
# Using as a project dependency
|
||||||
|
|
||||||
@@ -560,6 +632,5 @@ revision = origin/master
|
|||||||
```
|
```
|
||||||
Then simply fetch the dependency and it is ready to be used:
|
Then simply fetch the dependency and it is ready to be used:
|
||||||
```meson
|
```meson
|
||||||
ssp_sub = subproject('ssp')
|
ssp_dep = dependency('ssp')
|
||||||
ssp_dep = ssp_sub.get_variable('ssp_dep')
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ class converter {
|
|||||||
constexpr static auto string_error = setup<Matchers...>::string_error;
|
constexpr static auto string_error = setup<Matchers...>::string_error;
|
||||||
constexpr static auto default_delimiter = ",";
|
constexpr static auto default_delimiter = ",";
|
||||||
|
|
||||||
using error_type = ss::ternary_t<string_error, std::string, bool>;
|
using error_type = std::conditional_t<string_error, std::string, bool>;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// parses line with given delimiter, returns a 'T' object created with
|
// parses line with given delimiter, returns a 'T' object created with
|
||||||
@@ -194,7 +194,7 @@ private:
|
|||||||
////////////////
|
////////////////
|
||||||
|
|
||||||
const split_data& resplit(line_ptr_type new_line, ssize_t new_size,
|
const split_data& resplit(line_ptr_type new_line, ssize_t new_size,
|
||||||
const std::string& delim = default_delimiter) {
|
const std::string& delim) {
|
||||||
return splitter_.resplit(new_line, new_size, delim);
|
return splitter_.resplit(new_line, new_size, delim);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,7 +244,6 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void set_error_multiline_limit_reached() {
|
void set_error_multiline_limit_reached() {
|
||||||
if constexpr (string_error) {
|
if constexpr (string_error) {
|
||||||
error_.clear();
|
error_.clear();
|
||||||
@@ -274,7 +273,7 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_error_number_of_colums(size_t expected_pos, size_t pos) {
|
void set_error_number_of_columns(size_t expected_pos, size_t pos) {
|
||||||
if constexpr (string_error) {
|
if constexpr (string_error) {
|
||||||
error_.clear();
|
error_.clear();
|
||||||
error_.append("invalid number of columns, expected: ")
|
error_.append("invalid number of columns, expected: ")
|
||||||
@@ -286,6 +285,43 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void set_error_incompatible_mapping(size_t argument_size,
|
||||||
|
size_t mapping_size) {
|
||||||
|
if constexpr (string_error) {
|
||||||
|
error_.clear();
|
||||||
|
error_
|
||||||
|
.append(
|
||||||
|
"number of arguments does not match mapping, expected: ")
|
||||||
|
.append(std::to_string(mapping_size))
|
||||||
|
.append(", got: ")
|
||||||
|
.append(std::to_string(argument_size));
|
||||||
|
} else {
|
||||||
|
error_ = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
error_ = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
////////////////
|
////////////////
|
||||||
// convert implementation
|
// convert implementation
|
||||||
////////////////
|
////////////////
|
||||||
@@ -293,6 +329,7 @@ private:
|
|||||||
template <typename... Ts>
|
template <typename... Ts>
|
||||||
no_void_validator_tup_t<Ts...> convert_impl(const split_data& elems) {
|
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();
|
set_error_unterminated_quote();
|
||||||
@@ -300,10 +337,22 @@ private:
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sizeof...(Ts) != elems.size()) {
|
if (!columns_mapped()) {
|
||||||
set_error_number_of_colums(sizeof...(Ts), elems.size());
|
if (sizeof...(Ts) != elems.size()) {
|
||||||
no_void_validator_tup_t<Ts...> ret{};
|
set_error_number_of_columns(sizeof...(Ts), elems.size());
|
||||||
return ret;
|
return return_type{};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (sizeof...(Ts) != column_mappings_.size()) {
|
||||||
|
set_error_incompatible_mapping(sizeof...(Ts),
|
||||||
|
column_mappings_.size());
|
||||||
|
return return_type{};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elems.size() != number_of_columns_) {
|
||||||
|
set_error_number_of_columns(number_of_columns_, elems.size());
|
||||||
|
return return_type{};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return extract_tuple<Ts...>(elems);
|
return extract_tuple<Ts...>(elems);
|
||||||
@@ -316,6 +365,43 @@ private:
|
|||||||
return convert_impl<Ts...>(elems);
|
return convert_impl<Ts...>(elems);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////////////////
|
||||||
|
// column mapping
|
||||||
|
////////////////
|
||||||
|
|
||||||
|
bool columns_mapped() const {
|
||||||
|
return column_mappings_.size() != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t column_position(size_t tuple_position) const {
|
||||||
|
if (!columns_mapped()) {
|
||||||
|
return tuple_position;
|
||||||
|
}
|
||||||
|
return column_mappings_[tuple_position];
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_column_mapping(std::vector<size_t> positions,
|
||||||
|
size_t number_of_columns) {
|
||||||
|
if (positions.empty()) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear_column_positions() {
|
||||||
|
column_mappings_.clear();
|
||||||
|
number_of_columns_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
////////////////
|
////////////////
|
||||||
// conversion
|
// conversion
|
||||||
////////////////
|
////////////////
|
||||||
@@ -359,10 +445,10 @@ private:
|
|||||||
|
|
||||||
if constexpr (not_void) {
|
if constexpr (not_void) {
|
||||||
if constexpr (one_element) {
|
if constexpr (one_element) {
|
||||||
extract_one<elem_t>(tup, elems[ArgN], ArgN);
|
extract_one<elem_t>(tup, elems[column_position(ArgN)], ArgN);
|
||||||
} else {
|
} else {
|
||||||
auto& el = std::get<TupN>(tup);
|
auto& el = std::get<TupN>(tup);
|
||||||
extract_one<elem_t>(el, elems[ArgN], ArgN);
|
extract_one<elem_t>(el, elems[column_position(ArgN)], ArgN);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -390,6 +476,9 @@ private:
|
|||||||
|
|
||||||
template <typename...>
|
template <typename...>
|
||||||
friend class parser;
|
friend class parser;
|
||||||
|
|
||||||
|
std::vector<size_t> column_mappings_;
|
||||||
|
size_t number_of_columns_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* ss */
|
} /* ss */
|
||||||
|
|||||||
@@ -2,20 +2,27 @@
|
|||||||
|
|
||||||
#include "type_traits.hpp"
|
#include "type_traits.hpp"
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <fast_float/fast_float.h>
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <stdexcept>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
|
|
||||||
|
#ifndef SSP_DISABLE_FAST_FLOAT
|
||||||
|
#include <fast_float/fast_float.h>
|
||||||
|
#else
|
||||||
|
#include <charconv>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace ss {
|
namespace ss {
|
||||||
|
|
||||||
////////////////
|
////////////////
|
||||||
// number converters
|
// number converters
|
||||||
////////////////
|
////////////////
|
||||||
|
|
||||||
|
#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(
|
std::enable_if_t<std::is_floating_point_v<T>, std::optional<T>> to_num(
|
||||||
const char* const begin, const char* const end) {
|
const char* const begin, const char* const end) {
|
||||||
@@ -28,6 +35,22 @@ std::enable_if_t<std::is_floating_point_v<T>, std::optional<T>> to_num(
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
std::enable_if_t<std::is_floating_point_v<T>, std::optional<T>> to_num(
|
||||||
|
const char* const begin, const char* const end) {
|
||||||
|
T ret;
|
||||||
|
auto [ptr, ec] = std::from_chars(begin, end, ret);
|
||||||
|
|
||||||
|
if (ec != std::errc() || ptr != end) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
inline std::optional<short> from_char(char c) {
|
inline std::optional<short> from_char(char c) {
|
||||||
if (c >= '0' && c <= '9') {
|
if (c >= '0' && c <= '9') {
|
||||||
return c - '0';
|
return c - '0';
|
||||||
@@ -318,7 +341,14 @@ inline bool extract(const char* begin, const char* end, char& value) {
|
|||||||
|
|
||||||
template <>
|
template <>
|
||||||
inline bool extract(const char* begin, const char* end, std::string& value) {
|
inline bool extract(const char* begin, const char* end, std::string& value) {
|
||||||
value = std::string(begin, end);
|
value = std::string{begin, end};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline bool extract(const char* begin, const char* end,
|
||||||
|
std::string_view& value) {
|
||||||
|
value = std::string_view{begin, static_cast<size_t>(end - begin)};
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class parser {
|
|||||||
constexpr static auto string_error = setup<Matchers...>::string_error;
|
constexpr static auto string_error = setup<Matchers...>::string_error;
|
||||||
|
|
||||||
using multiline = typename setup<Matchers...>::multiline;
|
using multiline = typename setup<Matchers...>::multiline;
|
||||||
using error_type = ss::ternary_t<string_error, std::string, bool>;
|
using error_type = std::conditional_t<string_error, std::string, bool>;
|
||||||
|
|
||||||
constexpr static bool escaped_multiline_enabled =
|
constexpr static bool escaped_multiline_enabled =
|
||||||
multiline::enabled && setup<Matchers...>::escape::enabled;
|
multiline::enabled && setup<Matchers...>::escape::enabled;
|
||||||
@@ -25,12 +25,21 @@ class parser {
|
|||||||
constexpr static bool quoted_multiline_enabled =
|
constexpr static bool quoted_multiline_enabled =
|
||||||
multiline::enabled && setup<Matchers...>::quote::enabled;
|
multiline::enabled && setup<Matchers...>::quote::enabled;
|
||||||
|
|
||||||
|
constexpr static bool ignore_header = setup<Matchers...>::ignore_header;
|
||||||
|
|
||||||
|
constexpr static bool ignore_empty = setup<Matchers...>::ignore_empty;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
parser(const std::string& file_name,
|
parser(const std::string& file_name,
|
||||||
const std::string& delim = ss::default_delimiter)
|
const std::string& delim = ss::default_delimiter)
|
||||||
: file_name_{file_name}, reader_{file_name_, delim} {
|
: file_name_{file_name}, reader_{file_name_, delim} {
|
||||||
if (reader_.file_) {
|
if (reader_.file_) {
|
||||||
read_line();
|
read_line();
|
||||||
|
if constexpr (ignore_header) {
|
||||||
|
ignore_next();
|
||||||
|
} else {
|
||||||
|
header_ = reader_.get_next_row();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
set_error_file_not_open();
|
set_error_file_not_open();
|
||||||
eof_ = true;
|
eof_ = true;
|
||||||
@@ -57,15 +66,23 @@ public:
|
|||||||
return error_;
|
return error_;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool eof() const { return eof_; }
|
bool eof() const {
|
||||||
|
return eof_;
|
||||||
|
}
|
||||||
|
|
||||||
bool ignore_next() { return reader_.read_next(); }
|
bool ignore_next() {
|
||||||
|
return reader_.read_next();
|
||||||
|
}
|
||||||
|
|
||||||
template <typename T, typename... Ts>
|
template <typename T, typename... Ts>
|
||||||
T get_object() {
|
T get_object() {
|
||||||
return to_object<T>(get_next<Ts...>());
|
return to_object<T>(get_next<Ts...>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t line() const {
|
||||||
|
return valid() ? reader_.line_number_ - 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
template <typename T, typename... Ts>
|
template <typename T, typename... Ts>
|
||||||
no_void_validator_tup_t<T, Ts...> get_next() {
|
no_void_validator_tup_t<T, Ts...> get_next() {
|
||||||
reader_.update();
|
reader_.update();
|
||||||
@@ -85,6 +102,48 @@ public:
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool field_exists(const std::string& field) {
|
||||||
|
return header_index(field).has_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... Ts>
|
||||||
|
void use_fields(const Ts&... fields_args) {
|
||||||
|
if constexpr (ignore_header) {
|
||||||
|
set_error_header_ignored();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!valid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto fields = std::vector<std::string>{fields_args...};
|
||||||
|
std::vector<size_t> column_mappings;
|
||||||
|
|
||||||
|
for (const auto& field : fields) {
|
||||||
|
if (std::count(fields.begin(), fields.end(), field) != 1) {
|
||||||
|
set_error_field_used_multiple_times(field);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto index = header_index(field);
|
||||||
|
|
||||||
|
if (!index) {
|
||||||
|
set_error_invalid_field(field);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
column_mappings.push_back(*index);
|
||||||
|
}
|
||||||
|
|
||||||
|
reader_.converter_.set_column_mapping(column_mappings, header_.size());
|
||||||
|
reader_.next_line_converter_.set_column_mapping(column_mappings,
|
||||||
|
header_.size());
|
||||||
|
if (line() == 0) {
|
||||||
|
ignore_next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
////////////////
|
////////////////
|
||||||
// iterator
|
// iterator
|
||||||
////////////////
|
////////////////
|
||||||
@@ -92,14 +151,20 @@ public:
|
|||||||
template <bool get_object, typename T, typename... Ts>
|
template <bool get_object, typename T, typename... Ts>
|
||||||
struct iterable {
|
struct iterable {
|
||||||
struct iterator {
|
struct iterator {
|
||||||
using value =
|
using value = std::conditional_t<get_object, T,
|
||||||
ss::ternary_t<get_object, T, no_void_validator_tup_t<T, Ts...>>;
|
no_void_validator_tup_t<T, Ts...>>;
|
||||||
|
|
||||||
iterator() : parser_{nullptr} {}
|
iterator() : parser_{nullptr} {
|
||||||
iterator(parser<Matchers...>* parser) : parser_{parser} {}
|
}
|
||||||
|
iterator(parser<Matchers...>* parser) : parser_{parser} {
|
||||||
|
}
|
||||||
|
|
||||||
value& operator*() { return value_; }
|
value& operator*() {
|
||||||
value* operator->() { return &value_; }
|
return value_;
|
||||||
|
}
|
||||||
|
value* operator->() {
|
||||||
|
return &value_;
|
||||||
|
}
|
||||||
|
|
||||||
iterator& operator++() {
|
iterator& operator++() {
|
||||||
if (parser_->eof()) {
|
if (parser_->eof()) {
|
||||||
@@ -116,7 +181,9 @@ public:
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
iterator& operator++(int) { return ++*this; }
|
iterator& operator++(int) {
|
||||||
|
return ++*this;
|
||||||
|
}
|
||||||
|
|
||||||
friend bool operator==(const iterator& lhs, const iterator& rhs) {
|
friend bool operator==(const iterator& lhs, const iterator& rhs) {
|
||||||
return (lhs.parser_ == nullptr && rhs.parser_ == nullptr) ||
|
return (lhs.parser_ == nullptr && rhs.parser_ == nullptr) ||
|
||||||
@@ -133,10 +200,15 @@ public:
|
|||||||
parser<Matchers...>* parser_;
|
parser<Matchers...>* parser_;
|
||||||
};
|
};
|
||||||
|
|
||||||
iterable(parser<Matchers...>* parser) : parser_{parser} {}
|
iterable(parser<Matchers...>* parser) : parser_{parser} {
|
||||||
|
}
|
||||||
|
|
||||||
iterator begin() { return ++iterator{parser_}; }
|
iterator begin() {
|
||||||
iterator end() { return iterator{}; }
|
return ++iterator{parser_};
|
||||||
|
}
|
||||||
|
iterator end() {
|
||||||
|
return iterator{};
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
parser<Matchers...>* parser_;
|
parser<Matchers...>* parser_;
|
||||||
@@ -159,7 +231,8 @@ public:
|
|||||||
class composite {
|
class composite {
|
||||||
public:
|
public:
|
||||||
composite(std::tuple<Ts...>&& values, parser& parser)
|
composite(std::tuple<Ts...>&& values, parser& parser)
|
||||||
: values_{std::move(values)}, parser_{parser} {}
|
: values_{std::move(values)}, parser_{parser} {
|
||||||
|
}
|
||||||
|
|
||||||
// tries to convert the same line with a different output type
|
// tries to convert the same line with a different output type
|
||||||
// only if the previous conversion was not successful,
|
// only if the previous conversion was not successful,
|
||||||
@@ -185,7 +258,9 @@ public:
|
|||||||
return composite_with(std::move(value));
|
return composite_with(std::move(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::tuple<Ts...> values() { return values_; }
|
std::tuple<Ts...> values() {
|
||||||
|
return values_;
|
||||||
|
}
|
||||||
|
|
||||||
template <typename Fun>
|
template <typename Fun>
|
||||||
auto on_error(Fun&& fun) {
|
auto on_error(Fun&& fun) {
|
||||||
@@ -320,6 +395,20 @@ private:
|
|||||||
return {valid() ? std::move(value) : std::nullopt, *this};
|
return {valid() ? std::move(value) : std::nullopt, *this};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////////////////
|
||||||
|
// header
|
||||||
|
////////////////
|
||||||
|
|
||||||
|
std::optional<size_t> header_index(const std::string& field) {
|
||||||
|
auto it = std::find(header_.begin(), header_.end(), field);
|
||||||
|
|
||||||
|
if (it == header_.end()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::distance(header_.begin(), it);
|
||||||
|
}
|
||||||
|
|
||||||
////////////////
|
////////////////
|
||||||
// error
|
// error
|
||||||
////////////////
|
////////////////
|
||||||
@@ -371,15 +460,50 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void set_error_header_ignored() {
|
||||||
|
if constexpr (string_error) {
|
||||||
|
error_.append(file_name_)
|
||||||
|
.append(": \"")
|
||||||
|
.append("the header row is ignored within the setup, it cannot "
|
||||||
|
"be used")
|
||||||
|
.append("\"");
|
||||||
|
} else {
|
||||||
|
error_ = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_error_invalid_field(const std::string& field) {
|
||||||
|
if constexpr (string_error) {
|
||||||
|
error_.append(file_name_)
|
||||||
|
.append(": header does not contain given field: ")
|
||||||
|
.append(field);
|
||||||
|
} else {
|
||||||
|
error_ = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_error_field_used_multiple_times(const std::string& field) {
|
||||||
|
if constexpr (string_error) {
|
||||||
|
error_.append(file_name_)
|
||||||
|
.append(": given field used multiple times: ")
|
||||||
|
.append(field);
|
||||||
|
} else {
|
||||||
|
error_ = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
////////////////
|
////////////////
|
||||||
// line reading
|
// line reading
|
||||||
////////////////
|
////////////////
|
||||||
|
|
||||||
void read_line() { eof_ = !reader_.read_next(); }
|
void read_line() {
|
||||||
|
eof_ = !reader_.read_next();
|
||||||
|
}
|
||||||
|
|
||||||
struct reader {
|
struct reader {
|
||||||
reader(const std::string& file_name_, const std::string& delim)
|
reader(const std::string& file_name_, const std::string& delim)
|
||||||
: delim_{delim}, file_{fopen(file_name_.c_str(), "rb")} {}
|
: delim_{delim}, file_{fopen(file_name_.c_str(), "rb")} {
|
||||||
|
}
|
||||||
|
|
||||||
reader(reader&& other)
|
reader(reader&& other)
|
||||||
: buffer_{other.buffer_},
|
: buffer_{other.buffer_},
|
||||||
@@ -387,7 +511,7 @@ private:
|
|||||||
helper_buffer_{other.helper_buffer_}, converter_{std::move(
|
helper_buffer_{other.helper_buffer_}, converter_{std::move(
|
||||||
other.converter_)},
|
other.converter_)},
|
||||||
next_line_converter_{std::move(other.next_line_converter_)},
|
next_line_converter_{std::move(other.next_line_converter_)},
|
||||||
size_{other.size_}, next_line_size_{other.size_},
|
size_{other.size_}, next_line_size_{other.next_line_size_},
|
||||||
helper_size_{other.helper_size_}, delim_{std::move(other.delim_)},
|
helper_size_{other.helper_size_}, delim_{std::move(other.delim_)},
|
||||||
file_{other.file_}, crlf_{other.crlf_}, line_number_{
|
file_{other.file_}, crlf_{other.crlf_}, line_number_{
|
||||||
other.line_number_} {
|
other.line_number_} {
|
||||||
@@ -436,16 +560,27 @@ private:
|
|||||||
reader& operator=(const reader& other) = delete;
|
reader& operator=(const reader& other) = delete;
|
||||||
|
|
||||||
bool read_next() {
|
bool read_next() {
|
||||||
++line_number_;
|
|
||||||
memset(next_line_buffer_, '\0', next_line_size_);
|
|
||||||
ssize_t ssize =
|
|
||||||
get_line(&next_line_buffer_, &next_line_size_, file_);
|
|
||||||
|
|
||||||
if (ssize == -1) {
|
ssize_t ssize;
|
||||||
return false;
|
size_t size = 0;
|
||||||
|
while (size == 0) {
|
||||||
|
++line_number_;
|
||||||
|
if (next_line_size_ > 0) {
|
||||||
|
next_line_buffer_[0] = '\0';
|
||||||
|
}
|
||||||
|
ssize = get_line(&next_line_buffer_, &next_line_size_, file_);
|
||||||
|
|
||||||
|
if (ssize == -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
size = remove_eol(next_line_buffer_, ssize);
|
||||||
|
|
||||||
|
if constexpr (!ignore_empty) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t size = remove_eol(next_line_buffer_, ssize);
|
|
||||||
size_t limit = 0;
|
size_t limit = 0;
|
||||||
|
|
||||||
if constexpr (escaped_multiline_enabled) {
|
if constexpr (escaped_multiline_enabled) {
|
||||||
@@ -465,6 +600,8 @@ private:
|
|||||||
|
|
||||||
if constexpr (quoted_multiline_enabled) {
|
if constexpr (quoted_multiline_enabled) {
|
||||||
while (unterminated_quote()) {
|
while (unterminated_quote()) {
|
||||||
|
size -= next_line_converter_.size_shifted();
|
||||||
|
|
||||||
if (multiline_limit_reached(limit)) {
|
if (multiline_limit_reached(limit)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -488,7 +625,8 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
next_line_converter_.resplit(next_line_buffer_, size);
|
next_line_converter_.resplit(next_line_buffer_, size,
|
||||||
|
delim_);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -529,9 +667,6 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void undo_remove_eol(char* buffer, size_t& string_end) {
|
void undo_remove_eol(char* buffer, size_t& string_end) {
|
||||||
if (next_line_converter_.unterminated_quote()) {
|
|
||||||
string_end -= next_line_converter_.size_shifted();
|
|
||||||
}
|
|
||||||
if (crlf_) {
|
if (crlf_) {
|
||||||
std::copy_n("\r\n\0", 3, buffer + string_end);
|
std::copy_n("\r\n\0", 3, buffer + string_end);
|
||||||
string_end += 2;
|
string_end += 2;
|
||||||
@@ -578,6 +713,15 @@ private:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> get_next_row() const {
|
||||||
|
std::vector<std::string> next_row;
|
||||||
|
auto& next_row_raw = next_line_converter_.splitter_.split_data_;
|
||||||
|
for (const auto& [begin, end] : next_row_raw) {
|
||||||
|
next_row.emplace_back(begin, end);
|
||||||
|
}
|
||||||
|
return next_row;
|
||||||
|
}
|
||||||
|
|
||||||
////////////////
|
////////////////
|
||||||
// members
|
// members
|
||||||
////////////////
|
////////////////
|
||||||
@@ -606,6 +750,7 @@ private:
|
|||||||
std::string file_name_;
|
std::string file_name_;
|
||||||
error_type error_{};
|
error_type error_{};
|
||||||
reader reader_;
|
reader reader_;
|
||||||
|
std::vector<std::string> header_;
|
||||||
bool eof_{false};
|
bool eof_{false};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -109,10 +109,10 @@ 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 is cannot"
|
"the same matcher cannot"
|
||||||
"be defined multiple times");
|
"be defined multiple times");
|
||||||
using type = ternary_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>;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <template <char...> class Matcher>
|
template <template <char...> class Matcher>
|
||||||
@@ -149,8 +149,8 @@ struct get_multiline;
|
|||||||
|
|
||||||
template <typename T, typename... Ts>
|
template <typename T, typename... Ts>
|
||||||
struct get_multiline<T, Ts...> {
|
struct get_multiline<T, Ts...> {
|
||||||
using type = ternary_t<is_instance_of_multiline<T>::value, T,
|
using type = std::conditional_t<is_instance_of_multiline<T>::value, T,
|
||||||
typename get_multiline<Ts...>::type>;
|
typename get_multiline<Ts...>::type>;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
@@ -167,6 +167,18 @@ using get_multiline_t = typename get_multiline<Ts...>::type;
|
|||||||
|
|
||||||
class string_error;
|
class string_error;
|
||||||
|
|
||||||
|
////////////////
|
||||||
|
// ignore_header
|
||||||
|
////////////////
|
||||||
|
|
||||||
|
class ignore_header;
|
||||||
|
|
||||||
|
////////////////
|
||||||
|
// ignore_empty
|
||||||
|
////////////////
|
||||||
|
|
||||||
|
class ignore_empty;
|
||||||
|
|
||||||
////////////////
|
////////////////
|
||||||
// setup implementation
|
// setup implementation
|
||||||
////////////////
|
////////////////
|
||||||
@@ -185,13 +197,27 @@ private:
|
|||||||
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> {};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct is_ignore_header : std::is_same<T, ignore_header> {};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct is_ignore_empty : std::is_same<T, ignore_empty> {};
|
||||||
|
|
||||||
constexpr static auto count_matcher = count_v<is_matcher, Ts...>;
|
constexpr static auto count_matcher = count_v<is_matcher, Ts...>;
|
||||||
|
|
||||||
constexpr static auto count_multiline =
|
constexpr static auto count_multiline =
|
||||||
count_v<is_instance_of_multiline, Ts...>;
|
count_v<is_instance_of_multiline, Ts...>;
|
||||||
|
|
||||||
constexpr static auto count_string_error = count_v<is_string_error, Ts...>;
|
constexpr static auto count_string_error = count_v<is_string_error, Ts...>;
|
||||||
|
|
||||||
|
constexpr static auto count_ignore_header =
|
||||||
|
count_v<is_ignore_header, Ts...>;
|
||||||
|
|
||||||
|
constexpr static auto count_ignore_empty = count_v<is_ignore_empty, Ts...>;
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
using trim_left_only = get_matcher_t<trim_left, Ts...>;
|
using trim_left_only = get_matcher_t<trim_left, Ts...>;
|
||||||
using trim_right_only = get_matcher_t<trim_right, Ts...>;
|
using trim_right_only = get_matcher_t<trim_right, Ts...>;
|
||||||
@@ -201,11 +227,15 @@ public:
|
|||||||
using quote = get_matcher_t<quote, Ts...>;
|
using quote = get_matcher_t<quote, Ts...>;
|
||||||
using escape = get_matcher_t<escape, Ts...>;
|
using escape = get_matcher_t<escape, Ts...>;
|
||||||
|
|
||||||
using trim_left = ternary_t<trim_all::enabled, trim_all, trim_left_only>;
|
using trim_left =
|
||||||
using trim_right = ternary_t<trim_all::enabled, trim_all, trim_right_only>;
|
std::conditional_t<trim_all::enabled, trim_all, trim_left_only>;
|
||||||
|
using trim_right =
|
||||||
|
std::conditional_t<trim_all::enabled, trim_all, trim_right_only>;
|
||||||
|
|
||||||
using multiline = get_multiline_t<Ts...>;
|
using multiline = get_multiline_t<Ts...>;
|
||||||
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_empty = (count_ignore_empty == 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"
|
||||||
|
|||||||
@@ -23,10 +23,10 @@ private:
|
|||||||
constexpr static auto string_error = setup<Ts...>::string_error;
|
constexpr static auto string_error = setup<Ts...>::string_error;
|
||||||
constexpr static auto is_const_line = !quote::enabled && !escape::enabled;
|
constexpr static auto is_const_line = !quote::enabled && !escape::enabled;
|
||||||
|
|
||||||
using error_type = ss::ternary_t<string_error, std::string, bool>;
|
using error_type = std::conditional_t<string_error, std::string, bool>;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using line_ptr_type = ternary_t<is_const_line, const char*, char*>;
|
using line_ptr_type = std::conditional_t<is_const_line, const char*, char*>;
|
||||||
|
|
||||||
bool valid() const {
|
bool valid() const {
|
||||||
if constexpr (string_error) {
|
if constexpr (string_error) {
|
||||||
|
|||||||
@@ -357,26 +357,6 @@ struct is_instance_of<Template, Template<Ts...>> {
|
|||||||
template <template <typename...> class Template, typename... Ts>
|
template <template <typename...> class Template, typename... Ts>
|
||||||
constexpr bool is_instance_of_v = is_instance_of<Template, Ts...>::value;
|
constexpr bool is_instance_of_v = is_instance_of<Template, Ts...>::value;
|
||||||
|
|
||||||
////////////////
|
|
||||||
// ternary
|
|
||||||
////////////////
|
|
||||||
|
|
||||||
template <bool B, typename T, typename U>
|
|
||||||
struct ternary;
|
|
||||||
|
|
||||||
template <typename T, typename U>
|
|
||||||
struct ternary<true, T, U> {
|
|
||||||
using type = T;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename T, typename U>
|
|
||||||
struct ternary<false, T, U> {
|
|
||||||
using type = U;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <bool B, typename T, typename U>
|
|
||||||
using ternary_t = typename ternary<B, T, U>::type;
|
|
||||||
|
|
||||||
////////////////
|
////////////////
|
||||||
// tuple to struct
|
// tuple to struct
|
||||||
////////////////
|
////////////////
|
||||||
|
|||||||
19
meson.build
19
meson.build
@@ -1,17 +1,24 @@
|
|||||||
project('ssp', 'cpp',
|
project(
|
||||||
|
'ssp',
|
||||||
|
['cpp'],
|
||||||
default_options :
|
default_options :
|
||||||
['warning_level=3',
|
['warning_level=3',
|
||||||
'cpp_std=c++17',
|
'cpp_std=c++17',
|
||||||
'buildtype=debugoptimized'])
|
'buildtype=debugoptimized',
|
||||||
|
'wrap_mode=forcefallback'],
|
||||||
|
version: '1.4.0',
|
||||||
|
meson_version:'>=0.54.0')
|
||||||
|
|
||||||
fast_float_sub = subproject('fast_float')
|
fast_float_dep = dependency('fast_float')
|
||||||
fast_float_dep = fast_float_sub.get_variable('fast_float_dep')
|
|
||||||
|
|
||||||
ssp_dep = declare_dependency(
|
ssp_dep = declare_dependency(
|
||||||
include_directories: include_directories('include'),
|
include_directories: include_directories('include'),
|
||||||
dependencies: fast_float_dep
|
dependencies: fast_float_dep
|
||||||
)
|
)
|
||||||
|
|
||||||
|
meson.override_dependency('ssp', ssp_dep)
|
||||||
|
|
||||||
if not meson.is_subproject()
|
if not meson.is_subproject()
|
||||||
subdir('test')
|
subdir('test')
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ BUILD_TYPE=Debug
|
|||||||
|
|
||||||
set -eux
|
set -eux
|
||||||
|
|
||||||
git clone https://github.com/onqtam/doctest -b 2.4.4 --depth 1
|
git clone https://github.com/red0124/doctest -b master --depth 1
|
||||||
|
|
||||||
cmake -S doctest -B doctest/build \
|
cmake -S doctest -B doctest/build \
|
||||||
-D CMAKE_BUILD_TYPE=${BUILD_TYPE} \
|
-D CMAKE_BUILD_TYPE=${BUILD_TYPE} \
|
||||||
|
|||||||
34
script/single_header_generator.py
Executable file
34
script/single_header_generator.py
Executable file
@@ -0,0 +1,34 @@
|
|||||||
|
#!/bin/python3
|
||||||
|
|
||||||
|
headers_dir = 'include/ss/'
|
||||||
|
headers = ['type_traits.hpp',
|
||||||
|
'function_traits.hpp',
|
||||||
|
'restrictions.hpp',
|
||||||
|
'common.hpp',
|
||||||
|
'setup.hpp',
|
||||||
|
'splitter.hpp',
|
||||||
|
'extract.hpp',
|
||||||
|
'converter.hpp',
|
||||||
|
'parser.hpp']
|
||||||
|
|
||||||
|
combined_file = []
|
||||||
|
includes = []
|
||||||
|
|
||||||
|
for header in headers:
|
||||||
|
with open(headers_dir + header) as f:
|
||||||
|
for line in f.read().splitlines():
|
||||||
|
if '#include "' in line or '#include <fast_float' in line:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if '#include <' in line:
|
||||||
|
includes.append(line)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if '#pragma once' not in line:
|
||||||
|
combined_file.append(line)
|
||||||
|
|
||||||
|
includes = sorted(set(includes))
|
||||||
|
|
||||||
|
print('\n'.join(includes))
|
||||||
|
print('#define SSP_DISABLE_FAST_FLOAT')
|
||||||
|
print('\n'.join(combined_file))
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
[wrap-git]
|
[wrap-git]
|
||||||
url = https://github.com/onqtam/doctest
|
url = https://github.com/red0124/doctest
|
||||||
revision = 2.4.4
|
revision = master
|
||||||
|
|||||||
@@ -4,34 +4,38 @@ project(ssp_tests CXX)
|
|||||||
|
|
||||||
# ---- Dependencies ----
|
# ---- Dependencies ----
|
||||||
|
|
||||||
set(
|
|
||||||
ssp_INCLUDE_WITHOUT_SYSTEM
|
|
||||||
YES
|
|
||||||
CACHE
|
|
||||||
INTERNAL
|
|
||||||
"Turn the warning guard off to have errors appear in test builds"
|
|
||||||
)
|
|
||||||
|
|
||||||
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()
|
||||||
|
|
||||||
find_package(doctest 2.4.4 CONFIG REQUIRED)
|
include(FetchContent)
|
||||||
# for doctest_discover_tests
|
fetchcontent_declare(
|
||||||
include(doctest)
|
DOCTEST
|
||||||
|
GIT_REPOSITORY https://github.com/red0124/doctest
|
||||||
|
GIT_TAG origin/master
|
||||||
|
GIT_SHALLOW TRUE
|
||||||
|
)
|
||||||
|
|
||||||
|
fetchcontent_makeavailable(DOCTEST)
|
||||||
|
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_parser test_converter test_extractions)
|
||||||
add_executable("${name}" "${name}.cpp")
|
add_executable("${name}" "${name}.cpp")
|
||||||
target_link_libraries("${name}" PRIVATE ssp::ssp fast_float doctest::doctest)
|
target_link_libraries(
|
||||||
target_compile_definitions("${name}" PRIVATE
|
"${name}"
|
||||||
DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN CMAKE_GITHUB_CI)
|
PRIVATE ssp::ssp fast_float doctest::doctest
|
||||||
doctest_discover_tests("${name}")
|
)
|
||||||
|
target_compile_definitions(
|
||||||
|
"${name}"
|
||||||
|
PRIVATE DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN CMAKE_GITHUB_CI
|
||||||
|
)
|
||||||
|
add_test(NAME "${name}" COMMAND "${name}")
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ test_sources = files([
|
|||||||
'test_converter.cpp',
|
'test_converter.cpp',
|
||||||
'test_parser.cpp',
|
'test_parser.cpp',
|
||||||
'test_extractions.cpp',
|
'test_extractions.cpp',
|
||||||
|
'test_extractions_without_fast_float.cpp',
|
||||||
])
|
])
|
||||||
|
|
||||||
doctest_proj = subproject('doctest')
|
doctest_dep = dependency('doctest')
|
||||||
doctest_dep = doctest_proj.get_variable('doctest_dep')
|
|
||||||
|
|
||||||
test_exe = executable(
|
test_exe = executable(
|
||||||
'test_ssp',
|
'test_ssp',
|
||||||
|
|||||||
@@ -107,6 +107,13 @@ TEST_CASE("converter test valid conversions") {
|
|||||||
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<int, double>{5.5}, 6.6));
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
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"}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("converter test invalid conversions") {
|
TEST_CASE("converter test invalid conversions") {
|
||||||
|
|||||||
@@ -2,23 +2,6 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <ss/extract.hpp>
|
#include <ss/extract.hpp>
|
||||||
|
|
||||||
#define CHECK_FLOATING_CONVERSION(input, type) \
|
|
||||||
{ \
|
|
||||||
auto eps = std::numeric_limits<type>::min(); \
|
|
||||||
std::string s = #input; \
|
|
||||||
auto t = ss::to_num<type>(s.c_str(), s.c_str() + s.size()); \
|
|
||||||
REQUIRE(t.has_value()); \
|
|
||||||
CHECK_LT(std::abs(t.value() - type(input)), eps); \
|
|
||||||
} \
|
|
||||||
{ \
|
|
||||||
/* check negative too */ \
|
|
||||||
auto eps = std::numeric_limits<type>::min(); \
|
|
||||||
auto s = std::string("-") + #input; \
|
|
||||||
auto t = ss::to_num<type>(s.c_str(), s.c_str() + s.size()); \
|
|
||||||
REQUIRE(t.has_value()); \
|
|
||||||
CHECK_LT(std::abs(t.value() - type(-input)), eps); \
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
||||||
@@ -70,13 +53,6 @@ TEST_CASE("extract test functions for decimal values") {
|
|||||||
CHECK_DECIMAL_CONVERSION(1234567891011, ull);
|
CHECK_DECIMAL_CONVERSION(1234567891011, ull);
|
||||||
}
|
}
|
||||||
|
|
||||||
#define CHECK_INVALID_CONVERSION(input, type) \
|
|
||||||
{ \
|
|
||||||
std::string s = input; \
|
|
||||||
auto t = ss::to_num<type>(s.c_str(), s.c_str() + s.size()); \
|
|
||||||
CHECK_FALSE(t.has_value()); \
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("extract test functions for numbers with invalid inputs") {
|
TEST_CASE("extract test functions for numbers with invalid inputs") {
|
||||||
// negative unsigned value
|
// negative unsigned value
|
||||||
CHECK_INVALID_CONVERSION("-1234", ul);
|
CHECK_INVALID_CONVERSION("-1234", ul);
|
||||||
@@ -146,7 +122,7 @@ TEST_CASE("extract test functions for boolean values") {
|
|||||||
CHECK_EQ(v, b);
|
CHECK_EQ(v, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const std::string& s : {"2", "tru", "truee", "xxx", ""}) {
|
for (const std::string s : {"2", "tru", "truee", "xxx", ""}) {
|
||||||
bool v;
|
bool v;
|
||||||
CHECK_FALSE(ss::extract(s.c_str(), s.c_str() + s.size(), v));
|
CHECK_FALSE(ss::extract(s.c_str(), s.c_str() + s.size(), v));
|
||||||
}
|
}
|
||||||
@@ -160,7 +136,7 @@ TEST_CASE("extract test functions for char values") {
|
|||||||
CHECK_EQ(v, c);
|
CHECK_EQ(v, c);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const std::string& s : {"aa", "xxx", ""}) {
|
for (const std::string s : {"aa", "xxx", ""}) {
|
||||||
char v;
|
char v;
|
||||||
CHECK_FALSE(ss::extract(s.c_str(), s.c_str() + s.size(), v));
|
CHECK_FALSE(ss::extract(s.c_str(), s.c_str() + s.size(), v));
|
||||||
}
|
}
|
||||||
@@ -187,28 +163,19 @@ TEST_CASE("extract test functions for std::optional") {
|
|||||||
CHECK_EQ(*v, c);
|
CHECK_EQ(*v, c);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const std::string& s : {"aa", "xxx", ""}) {
|
for (const std::string s : {"aa", "xxx", ""}) {
|
||||||
std::optional<int> v;
|
std::optional<int> 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());
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const std::string& s : {"aa", "xxx", ""}) {
|
for (const std::string s : {"aa", "xxx", ""}) {
|
||||||
std::optional<char> v;
|
std::optional<char> 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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#define REQUIRE_VARIANT(var, el, type) \
|
|
||||||
{ \
|
|
||||||
auto ptr = std::get_if<type>(&var); \
|
|
||||||
REQUIRE(ptr); \
|
|
||||||
REQUIRE_EQ(el, *ptr); \
|
|
||||||
}
|
|
||||||
|
|
||||||
#define CHECK_NOT_VARIANT(var, type) CHECK(!std::holds_alternative<type>(var));
|
|
||||||
|
|
||||||
TEST_CASE("extract test functions for std::variant") {
|
TEST_CASE("extract test functions for std::variant") {
|
||||||
{
|
{
|
||||||
std::string s = "22";
|
std::string s = "22";
|
||||||
|
|||||||
132
test/test_extractions_without_fast_float.cpp
Normal file
132
test/test_extractions_without_fast_float.cpp
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
#include "test_helpers.hpp"
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#define SSP_DISABLE_FAST_FLOAT
|
||||||
|
#include <ss/extract.hpp>
|
||||||
|
|
||||||
|
TEST_CASE(
|
||||||
|
"testing extract functions for floating point values without fast float") {
|
||||||
|
CHECK_FLOATING_CONVERSION(123.456, float);
|
||||||
|
CHECK_FLOATING_CONVERSION(123.456, double);
|
||||||
|
|
||||||
|
CHECK_FLOATING_CONVERSION(69, float);
|
||||||
|
CHECK_FLOATING_CONVERSION(69, double);
|
||||||
|
|
||||||
|
CHECK_FLOATING_CONVERSION(420., float);
|
||||||
|
CHECK_FLOATING_CONVERSION(420., double);
|
||||||
|
|
||||||
|
CHECK_FLOATING_CONVERSION(0.123, float);
|
||||||
|
CHECK_FLOATING_CONVERSION(0.123, double);
|
||||||
|
|
||||||
|
CHECK_FLOATING_CONVERSION(123e4, float);
|
||||||
|
CHECK_FLOATING_CONVERSION(123e4, double);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("extract test functions for numbers with invalid inputs without fast "
|
||||||
|
"float") {
|
||||||
|
// floating pint for int
|
||||||
|
CHECK_INVALID_CONVERSION("123.4", int);
|
||||||
|
|
||||||
|
// random input for float
|
||||||
|
CHECK_INVALID_CONVERSION("xxx1", float);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("extract test functions for std::variant without fast float") {
|
||||||
|
{
|
||||||
|
std::string s = "22";
|
||||||
|
{
|
||||||
|
std::variant<int, double, std::string> var;
|
||||||
|
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
|
||||||
|
CHECK_NOT_VARIANT(var, double);
|
||||||
|
CHECK_NOT_VARIANT(var, std::string);
|
||||||
|
REQUIRE_VARIANT(var, 22, int);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::variant<double, int, std::string> var;
|
||||||
|
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
|
||||||
|
CHECK_NOT_VARIANT(var, int);
|
||||||
|
CHECK_NOT_VARIANT(var, std::string);
|
||||||
|
REQUIRE_VARIANT(var, 22, double);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::variant<std::string, double, int> var;
|
||||||
|
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
|
||||||
|
CHECK_NOT_VARIANT(var, int);
|
||||||
|
CHECK_NOT_VARIANT(var, double);
|
||||||
|
REQUIRE_VARIANT(var, "22", std::string);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::variant<int> var;
|
||||||
|
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
|
||||||
|
REQUIRE_VARIANT(var, 22, int);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::string s = "22.2";
|
||||||
|
{
|
||||||
|
std::variant<int, double, std::string> var;
|
||||||
|
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
|
||||||
|
CHECK_NOT_VARIANT(var, int);
|
||||||
|
CHECK_NOT_VARIANT(var, std::string);
|
||||||
|
REQUIRE_VARIANT(var, 22.2, double);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::variant<double, int, std::string> var;
|
||||||
|
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
|
||||||
|
CHECK_NOT_VARIANT(var, int);
|
||||||
|
CHECK_NOT_VARIANT(var, std::string);
|
||||||
|
REQUIRE_VARIANT(var, 22.2, double);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::variant<std::string, double, int> var;
|
||||||
|
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
|
||||||
|
CHECK_NOT_VARIANT(var, int);
|
||||||
|
CHECK_NOT_VARIANT(var, double);
|
||||||
|
REQUIRE_VARIANT(var, "22.2", std::string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::string s = "2.2.2";
|
||||||
|
{
|
||||||
|
std::variant<int, double, std::string> var;
|
||||||
|
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
|
||||||
|
CHECK_NOT_VARIANT(var, int);
|
||||||
|
CHECK_NOT_VARIANT(var, double);
|
||||||
|
REQUIRE_VARIANT(var, "2.2.2", std::string);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::variant<double, std::string, int> var;
|
||||||
|
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
|
||||||
|
CHECK_NOT_VARIANT(var, int);
|
||||||
|
CHECK_NOT_VARIANT(var, double);
|
||||||
|
REQUIRE_VARIANT(var, "2.2.2", std::string);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::variant<std::string, double, int> var;
|
||||||
|
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
|
||||||
|
CHECK_NOT_VARIANT(var, int);
|
||||||
|
CHECK_NOT_VARIANT(var, double);
|
||||||
|
REQUIRE_VARIANT(var, "2.2.2", std::string);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::variant<int, double> var;
|
||||||
|
REQUIRE_FALSE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
|
||||||
|
|
||||||
|
REQUIRE_VARIANT(var, int{}, int);
|
||||||
|
CHECK_NOT_VARIANT(var, double);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::variant<double, int> var;
|
||||||
|
REQUIRE_FALSE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
|
||||||
|
|
||||||
|
REQUIRE_VARIANT(var, double{}, double);
|
||||||
|
CHECK_NOT_VARIANT(var, int);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::variant<int> var;
|
||||||
|
REQUIRE_FALSE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
|
||||||
|
|
||||||
|
REQUIRE_VARIANT(var, int{}, int);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <cstdlib>
|
|
||||||
#include <cstring>
|
#include <string>
|
||||||
|
|
||||||
#ifdef CMAKE_GITHUB_CI
|
#ifdef CMAKE_GITHUB_CI
|
||||||
#include <doctest/doctest.h>
|
#include <doctest/doctest.h>
|
||||||
@@ -9,40 +9,55 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
|
#define CHECK_FLOATING_CONVERSION(input, type) \
|
||||||
|
{ \
|
||||||
|
auto eps = std::numeric_limits<type>::min(); \
|
||||||
|
std::string s = #input; \
|
||||||
|
auto t = ss::to_num<type>(s.c_str(), s.c_str() + s.size()); \
|
||||||
|
REQUIRE(t.has_value()); \
|
||||||
|
CHECK_LT(std::abs(t.value() - type(input)), eps); \
|
||||||
|
} \
|
||||||
|
{ \
|
||||||
|
/* check negative too */ \
|
||||||
|
auto eps = std::numeric_limits<type>::min(); \
|
||||||
|
auto s = std::string("-") + #input; \
|
||||||
|
auto t = ss::to_num<type>(s.c_str(), s.c_str() + s.size()); \
|
||||||
|
REQUIRE(t.has_value()); \
|
||||||
|
CHECK_LT(std::abs(t.value() - type(-input)), eps); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define CHECK_INVALID_CONVERSION(input, type) \
|
||||||
|
{ \
|
||||||
|
std::string s = input; \
|
||||||
|
auto t = ss::to_num<type>(s.c_str(), s.c_str() + s.size()); \
|
||||||
|
CHECK_FALSE(t.has_value()); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define REQUIRE_VARIANT(var, el, type) \
|
||||||
|
{ \
|
||||||
|
auto ptr = std::get_if<type>(&var); \
|
||||||
|
REQUIRE(ptr); \
|
||||||
|
REQUIRE_EQ(el, *ptr); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define CHECK_NOT_VARIANT(var, type) CHECK(!std::holds_alternative<type>(var));
|
||||||
|
|||||||
@@ -21,9 +21,12 @@ struct unique_file_name {
|
|||||||
|
|
||||||
unique_file_name()
|
unique_file_name()
|
||||||
: name{"random_" + std::to_string(i++) + time_now_rand() +
|
: name{"random_" + std::to_string(i++) + time_now_rand() +
|
||||||
"_file.csv"} {}
|
"_file.csv"} {
|
||||||
|
}
|
||||||
|
|
||||||
~unique_file_name() { std::filesystem::remove(name); }
|
~unique_file_name() {
|
||||||
|
std::filesystem::remove(name);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
void replace_all(std::string& s, const std::string& from,
|
void replace_all(std::string& s, const std::string& from,
|
||||||
@@ -46,18 +49,25 @@ void update_if_crlf(std::string& s) {
|
|||||||
|
|
||||||
struct X {
|
struct X {
|
||||||
constexpr static auto delim = ",";
|
constexpr static auto delim = ",";
|
||||||
|
constexpr static auto make_empty = "_EMPTY_";
|
||||||
int i;
|
int i;
|
||||||
double d;
|
double d;
|
||||||
std::string s;
|
std::string s;
|
||||||
|
|
||||||
std::string to_string() const {
|
std::string to_string() const {
|
||||||
|
if (s == make_empty) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
return std::to_string(i)
|
return std::to_string(i)
|
||||||
.append(delim)
|
.append(delim)
|
||||||
.append(std::to_string(d))
|
.append(std::to_string(d))
|
||||||
.append(delim)
|
.append(delim)
|
||||||
.append(s);
|
.append(s);
|
||||||
}
|
}
|
||||||
auto tied() const { return std::tie(i, d, s); }
|
auto tied() const {
|
||||||
|
return std::tie(i, d, s);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
@@ -68,7 +78,8 @@ std::enable_if_t<ss::has_m_tied_t<T>, bool> operator==(const T& lhs,
|
|||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
static void make_and_write(const std::string& file_name,
|
static void make_and_write(const std::string& file_name,
|
||||||
const std::vector<T>& data) {
|
const std::vector<T>& data,
|
||||||
|
const std::vector<std::string>& header = {}) {
|
||||||
std::ofstream out{file_name};
|
std::ofstream out{file_name};
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
@@ -77,6 +88,17 @@ static void make_and_write(const std::string& file_name,
|
|||||||
std::vector<const char*> new_lines = {"\n", "\r\n"};
|
std::vector<const char*> new_lines = {"\n", "\r\n"};
|
||||||
#endif
|
#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) {
|
for (size_t i = 0; i < data.size(); ++i) {
|
||||||
out << data[i].to_string() << new_lines[i % new_lines.size()];
|
out << data[i].to_string() << new_lines[i % new_lines.size()];
|
||||||
}
|
}
|
||||||
@@ -261,7 +283,7 @@ TEST_CASE("parser test various cases") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::copy_if(data.begin(), data.end(), expected.begin(),
|
std::copy_if(data.begin(), data.end(), expected.begin(),
|
||||||
[](const X& x) { return x.i != excluded; });
|
[&](const X& x) { return x.i != excluded; });
|
||||||
CHECK_EQ(i, expected);
|
CHECK_EQ(i, expected);
|
||||||
CHECK_EQ(i2, expected);
|
CHECK_EQ(i2, expected);
|
||||||
}
|
}
|
||||||
@@ -322,10 +344,13 @@ struct test_struct {
|
|||||||
int i;
|
int i;
|
||||||
double d;
|
double d;
|
||||||
char c;
|
char c;
|
||||||
auto tied() { return std::tie(i, d, c); }
|
auto tied() {
|
||||||
|
return std::tie(i, d, c);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
void expect_test_struct(const test_struct&) {}
|
void expect_test_struct(const test_struct&) {
|
||||||
|
}
|
||||||
|
|
||||||
// various scenarios
|
// various scenarios
|
||||||
TEST_CASE("parser test composite conversion") {
|
TEST_CASE("parser test composite conversion") {
|
||||||
@@ -353,7 +378,7 @@ TEST_CASE("parser test composite conversion") {
|
|||||||
p.try_next<int, int, double>(fail)
|
p.try_next<int, int, double>(fail)
|
||||||
.or_else<test_struct>(fail)
|
.or_else<test_struct>(fail)
|
||||||
.or_else<int, char, double>(
|
.or_else<int, char, double>(
|
||||||
[](auto&& data) { CHECK_EQ(data, expectedData); })
|
[&](auto&& data) { CHECK_EQ(data, expectedData); })
|
||||||
.on_error(fail)
|
.on_error(fail)
|
||||||
.or_else<test_tuple>(fail)
|
.or_else<test_tuple>(fail)
|
||||||
.values();
|
.values();
|
||||||
@@ -371,7 +396,7 @@ TEST_CASE("parser test composite conversion") {
|
|||||||
constexpr static auto expectedData = std::tuple{10, 20, 11.1};
|
constexpr static auto expectedData = std::tuple{10, 20, 11.1};
|
||||||
|
|
||||||
auto [d1, d2, d3, d4] =
|
auto [d1, d2, d3, d4] =
|
||||||
p.try_next<int, int, double>([](auto& i1, auto i2, double d) {
|
p.try_next<int, int, double>([&](auto& i1, auto i2, double d) {
|
||||||
CHECK_EQ(std::tie(i1, i2, d), expectedData);
|
CHECK_EQ(std::tie(i1, i2, d), expectedData);
|
||||||
})
|
})
|
||||||
.on_error(fail)
|
.on_error(fail)
|
||||||
@@ -539,26 +564,24 @@ TEST_CASE("parser test composite conversion") {
|
|||||||
CHECK(p.eof());
|
CHECK(p.eof());
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t move_called = 0;
|
|
||||||
|
|
||||||
struct my_string {
|
struct my_string {
|
||||||
char* data{nullptr};
|
char* data{nullptr};
|
||||||
|
|
||||||
my_string() = default;
|
my_string() = default;
|
||||||
|
|
||||||
~my_string() { delete[] data; }
|
~my_string() {
|
||||||
|
delete[] data;
|
||||||
|
}
|
||||||
|
|
||||||
// make sure no object is copied
|
// make sure no object is copied
|
||||||
my_string(const my_string&) = delete;
|
my_string(const my_string&) = delete;
|
||||||
my_string& operator=(const my_string&) = delete;
|
my_string& operator=(const my_string&) = delete;
|
||||||
|
|
||||||
my_string(my_string&& other) : data{other.data} {
|
my_string(my_string&& other) : data{other.data} {
|
||||||
move_called++;
|
|
||||||
other.data = nullptr;
|
other.data = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
my_string& operator=(my_string&& other) {
|
my_string& operator=(my_string&& other) {
|
||||||
move_called++;
|
|
||||||
data = other.data;
|
data = other.data;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
@@ -577,55 +600,11 @@ struct xyz {
|
|||||||
my_string x;
|
my_string x;
|
||||||
my_string y;
|
my_string y;
|
||||||
my_string z;
|
my_string z;
|
||||||
auto tied() { return std::tie(x, y, z); }
|
auto tied() {
|
||||||
|
return std::tie(x, y, z);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
TEST_CASE("parser test the moving of parsed values") {
|
|
||||||
size_t move_called_one_col;
|
|
||||||
|
|
||||||
{
|
|
||||||
unique_file_name f;
|
|
||||||
{
|
|
||||||
std::ofstream out{f.name};
|
|
||||||
out << "x" << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
ss::parser p{f.name, ","};
|
|
||||||
auto x = p.get_next<my_string>();
|
|
||||||
CHECK_LT(move_called, 3);
|
|
||||||
move_called_one_col = move_called;
|
|
||||||
move_called = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
unique_file_name f;
|
|
||||||
{
|
|
||||||
std::ofstream out{f.name};
|
|
||||||
out << "a,b,c" << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
|
|
||||||
ss::parser p{f.name, ","};
|
|
||||||
auto x = p.get_next<my_string, my_string, my_string>();
|
|
||||||
CHECK_LE(move_called, 3 * move_called_one_col);
|
|
||||||
move_called = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
ss::parser p{f.name, ","};
|
|
||||||
auto x = p.get_object<xyz, my_string, my_string, my_string>();
|
|
||||||
CHECK_LE(move_called, 6 * move_called_one_col);
|
|
||||||
move_called = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
ss::parser p{f.name, ","};
|
|
||||||
auto x = p.get_next<xyz>();
|
|
||||||
CHECK_LE(move_called, 6 * move_called_one_col);
|
|
||||||
move_called = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("parser test the moving of parsed composite values") {
|
TEST_CASE("parser test the moving of parsed composite values") {
|
||||||
// to compile is enough
|
// to compile is enough
|
||||||
return;
|
return;
|
||||||
@@ -832,3 +811,280 @@ TEST_CASE("parser test multiline restricted") {
|
|||||||
}
|
}
|
||||||
CHECK_EQ(i, data);
|
CHECK_EQ(i, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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>...> {};
|
||||||
|
|
||||||
|
void checkSize(size_t size1, size_t size2) {
|
||||||
|
CHECK_EQ(size1, size2);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... Ts>
|
||||||
|
void testFields(const std::string file_name, const std::vector<X>& data,
|
||||||
|
const std::vector<std::string>& fields) {
|
||||||
|
using CaseType = std::tuple<Ts...>;
|
||||||
|
|
||||||
|
ss::parser p{file_name, ","};
|
||||||
|
CHECK_FALSE(p.field_exists("Unknown"));
|
||||||
|
p.use_fields(fields);
|
||||||
|
std::vector<CaseType> i;
|
||||||
|
|
||||||
|
for (const auto& a : p.iterate<CaseType>()) {
|
||||||
|
i.push_back(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
checkSize(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("parser test various cases with header") {
|
||||||
|
unique_file_name f;
|
||||||
|
constexpr static auto Int = "Int";
|
||||||
|
constexpr static auto Dbl = "Double";
|
||||||
|
constexpr static auto Str = "String";
|
||||||
|
using str = std::string;
|
||||||
|
|
||||||
|
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>()) {
|
||||||
|
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>()) {
|
||||||
|
i.emplace_back(ss::to_object<X>(a));
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK_EQ(i, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
ss::parser<ss::ignore_header> p{f.name, ","};
|
||||||
|
std::vector<X> i;
|
||||||
|
|
||||||
|
for (const auto& a : p.iterate<int, double, std::string>()) {
|
||||||
|
i.emplace_back(ss::to_object<X>(a));
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK_EQ(i, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
ss::parser<ss::ignore_header, ss::string_error> p{f.name, ","};
|
||||||
|
p.use_fields(Int, Dbl, Str);
|
||||||
|
CHECK_FALSE(p.valid());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
ss::parser<ss::ignore_header, ss::string_error> p{f.name, ","};
|
||||||
|
CHECK_FALSE(p.field_exists("Unknown"));
|
||||||
|
|
||||||
|
p.use_fields(Int, "Unknown");
|
||||||
|
CHECK_FALSE(p.valid());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
ss::parser<ss::ignore_header, ss::string_error> p{f.name, ","};
|
||||||
|
p.use_fields(Int, Int);
|
||||||
|
CHECK_FALSE(p.valid());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
ss::parser<ss::string_error> p{f.name, ","};
|
||||||
|
p.use_fields(Int, Dbl);
|
||||||
|
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
|
||||||
|
{
|
||||||
|
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, {' + ', '.join(arg_params) + '});'
|
||||||
|
print(call)
|
||||||
|
*/
|
||||||
|
|
||||||
|
testFields<str>(o, d, {Str});
|
||||||
|
testFields<int>(o, d, {Int});
|
||||||
|
testFields<double>(o, d, {Dbl});
|
||||||
|
testFields<str, int>(o, d, {Str, Int});
|
||||||
|
testFields<str, double>(o, d, {Str, Dbl});
|
||||||
|
testFields<int, str>(o, d, {Int, Str});
|
||||||
|
testFields<int, double>(o, d, {Int, Dbl});
|
||||||
|
testFields<double, str>(o, d, {Dbl, Str});
|
||||||
|
testFields<double, int>(o, d, {Dbl, Int});
|
||||||
|
testFields<str, int, double>(o, d, {Str, Int, Dbl});
|
||||||
|
testFields<str, double, int>(o, d, {Str, Dbl, Int});
|
||||||
|
testFields<int, str, double>(o, d, {Int, Str, Dbl});
|
||||||
|
testFields<int, double, str>(o, d, {Int, Dbl, Str});
|
||||||
|
testFields<double, str, int>(o, d, {Dbl, Str, Int});
|
||||||
|
testFields<double, int, str>(o, d, {Dbl, Int, Str});
|
||||||
|
}
|
||||||
|
|
||||||
|
void testIgnoreEmpty(const std::vector<X>& data) {
|
||||||
|
unique_file_name f;
|
||||||
|
make_and_write(f.name, data);
|
||||||
|
|
||||||
|
std::vector<X> expected;
|
||||||
|
for (const auto& d : data) {
|
||||||
|
if (d.s != X::make_empty) {
|
||||||
|
expected.push_back(d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
ss::parser<ss::string_error, ss::ignore_empty> p{f.name, ","};
|
||||||
|
|
||||||
|
std::vector<X> i;
|
||||||
|
for (const auto& a : p.iterate<X>()) {
|
||||||
|
i.push_back(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK_EQ(i, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
ss::parser<ss::string_error> p{f.name, ","};
|
||||||
|
std::vector<X> i;
|
||||||
|
size_t n = 0;
|
||||||
|
for (const auto& a : p.iterate<X>()) {
|
||||||
|
if (data.at(n).s == X::make_empty) {
|
||||||
|
CHECK_FALSE(p.valid());
|
||||||
|
}
|
||||||
|
i.push_back(a);
|
||||||
|
++n;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data != expected) {
|
||||||
|
CHECK_NE(i, expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("parser test various cases with empty lines") {
|
||||||
|
testIgnoreEmpty({{1, 2, "x"}, {3, 4, "y"}, {9, 10, "v"}, {11, 12, "w"}});
|
||||||
|
|
||||||
|
testIgnoreEmpty(
|
||||||
|
{{1, 2, X::make_empty}, {3, 4, "y"}, {9, 10, "v"}, {11, 12, "w"}});
|
||||||
|
|
||||||
|
testIgnoreEmpty(
|
||||||
|
{{1, 2, "x"}, {3, 4, "y"}, {9, 10, "v"}, {11, 12, X::make_empty}});
|
||||||
|
|
||||||
|
testIgnoreEmpty(
|
||||||
|
{{1, 2, "x"}, {5, 6, X::make_empty}, {9, 10, "v"}, {11, 12, "w"}});
|
||||||
|
|
||||||
|
testIgnoreEmpty({{1, 2, X::make_empty},
|
||||||
|
{5, 6, X::make_empty},
|
||||||
|
{9, 10, "v"},
|
||||||
|
{11, 12, "w"}});
|
||||||
|
|
||||||
|
testIgnoreEmpty({{1, 2, X::make_empty},
|
||||||
|
{3, 4, "y"},
|
||||||
|
{9, 10, "v"},
|
||||||
|
{11, 12, X::make_empty}});
|
||||||
|
|
||||||
|
testIgnoreEmpty({{1, 2, "x"},
|
||||||
|
{3, 4, "y"},
|
||||||
|
{9, 10, X::make_empty},
|
||||||
|
{11, 12, X::make_empty}});
|
||||||
|
|
||||||
|
testIgnoreEmpty({{1, 2, X::make_empty},
|
||||||
|
{3, 4, "y"},
|
||||||
|
{9, 10, X::make_empty},
|
||||||
|
{11, 12, X::make_empty}});
|
||||||
|
|
||||||
|
testIgnoreEmpty({{1, 2, X::make_empty},
|
||||||
|
{3, 4, X::make_empty},
|
||||||
|
{9, 10, X::make_empty},
|
||||||
|
{11, 12, X::make_empty}});
|
||||||
|
|
||||||
|
testIgnoreEmpty({{1, 2, "x"},
|
||||||
|
{3, 4, X::make_empty},
|
||||||
|
{9, 10, X::make_empty},
|
||||||
|
{11, 12, X::make_empty}});
|
||||||
|
|
||||||
|
testIgnoreEmpty({{1, 2, X::make_empty},
|
||||||
|
{3, 4, X::make_empty},
|
||||||
|
{9, 10, X::make_empty},
|
||||||
|
{11, 12, "w"}});
|
||||||
|
|
||||||
|
testIgnoreEmpty({{11, 12, X::make_empty}});
|
||||||
|
|
||||||
|
testIgnoreEmpty({});
|
||||||
|
}
|
||||||
|
|||||||
14
test/test_single_header.sh
Executable file
14
test/test_single_header.sh
Executable file
@@ -0,0 +1,14 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -x
|
||||||
|
set -e
|
||||||
|
|
||||||
|
python3 script/single_header_generator.py > ssp.cpp
|
||||||
|
|
||||||
|
echo 'int main(){ ss::parser p{""}; p.get_next<int, float>(); return 0; }' \
|
||||||
|
>> ssp.cpp
|
||||||
|
|
||||||
|
g++ -std=c++17 ssp.cpp -o ssp.bin -Wall -Wextra
|
||||||
|
./ssp.bin
|
||||||
|
|
||||||
|
rm ssp.cpp ssp.bin
|
||||||
Reference in New Issue
Block a user