update documentation, fix minor bug

This commit is contained in:
ado 2021-01-01 18:38:53 +01:00
parent aa97b18aa7
commit aec49e7e4f
2 changed files with 81 additions and 27 deletions

View File

@ -85,30 +85,92 @@ the **set_error_mode** method:
void parser::set_error_mode(ss::error_mode); void parser::set_error_mode(ss::error_mode);
const std::string& parser::error_msg(); const std::string& parser::error_msg();
bool parser::valid(); bool parser::valid();
bool parser::eof();
``` ```
Error messages can always be disabled by setting the error mode to Error messages can always be disabled by setting the error mode to
**error_mode::Bool**. An error can be detected using the **valid** method which **error_mode::Bool**. An error can be detected using the **valid** method which
will return **false** if the file could not be opened, or if the conversion would return **false** if the file could not be opened, or if the conversion
could not be made (invalid types, invalid number of columns, ...). 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.
## Conversions ## Conversions
The above example will be used to show some of the features of the library. The above example will be used to show some of the features of the library.
As seen above, the **get_next** method returns a tuple of objects specified As seen above, the **get_next** method returns a tuple of objects specified
inside the template type list. inside the template type list.
We could pass **void** as the second template parameter to ignore the second (age) If a conversion could not be applied, the method would return a tuple of
column in our csv, a tuple of only 2 parameters will be retuned: default constructed objects, and **valid** 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 **get_next** is called with a **tuple** it would behave identically to passing
the same tuple parameters to **get_next**:
```cpp
using student = std::tuple<std::string, int, double>;
// returns std::tuple<std::string, int, double>
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, ...)*
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
struct student {
std::string name;
int age;
double grade;
};
```
```cpp
// returns student
auto student = p.get_object<student, std::string, int, double>();
```
This works with any object if the constructor could be invoked using the
template arguments given to **get_object**:
```cpp
// returns std::vector<std::string> containing 3 elements
auto vec = p.get_object<std::vector<std::string>, std::string, std::string,
std::string>();
```
And finally, using something I personally like to do, a struct (class) with a **tied**
method witch returns a tuple of references to to the members of the struct.
```cpp
struct student {
std::string name;
int age;
double 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.
```cpp
// returns student
auto s = p.get_next<student>();
```
*Note, the order in which the members of the tied method are returned must
match the order of the elements in the csv*
### 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:
```cpp ```cpp
// returns std::tuple<std::string, double> // returns std::tuple<std::string, double>
auto [name, grade] = p.get_next<std::string, void, double>(); auto [name, grade] = p.get_next<std::string, void, double>();
``` ```
To ignore a whole row, **ignore_next** could be used, returns **false** if **eof**:
If a conversion could not be applied, the method would return a tuple of ```cpp
default constructed objects, and **valid** would return **false** , for example bool parser::ignore_next();
if the third (grade) column in our csv could not be converted to a double ```
the conversion would fail. **std::optional<double>** could be passed if we **std::optional** could be passed if we wanted the conversion to proceed in the
wanted the conversion to proceed in the case of a failure: 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<double>>
@ -117,11 +179,9 @@ if(grade) {
// do something with grade // do something with grade
} }
``` ```
Similar to **std::optional**, **std::variant** could be used to try other Similar to **std::optional**, **std::variant** could be used to try other
conversions if the previous failed _(note: conversion to **std::string** will conversions if the previous failed _(Note, conversion to std::string will
always pass)_: always pass)_:
```cpp ```cpp
// returns std::tuple<std::string, int, std::variant<double, char>> // returns std::tuple<std::string, int, std::variant<double, char>>
auto [name, age, grade] = auto [name, age, grade] =
@ -132,24 +192,20 @@ if(std::holds_alternative<double>(grade)) {
// grade set as char // grade set as char
} }
``` ```
### Restrictions
## Restrictions
Custom **restrictions** can be used to narrow down the conversions of unwanted 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: values. **ss::ir** (in range) and **ss::ne** (none empty) are one of those:
```cpp ```cpp
// returns std::tuple<std::string, int, double>
// 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>
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<double, 0, 10>>();
``` ```
If the restrictions are not met, the conversion will fail. If the restrictions are not met, the conversion will fail.
Other predefined restrictions are **ss::ax** (all except), **ss::nx** (none except) Other predefined restrictions are **ss::ax** (all except), **ss::nx** (none except)
and **ss::oor** (out of range): and **ss::oor** (out of range):
```cpp ```cpp
// all ints exept 10 and 20 // all ints exept 10 and 20
ss::ax<int, 10, 20> ss::ax<int, 10, 20>
@ -158,13 +214,11 @@ 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 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 **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** 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 and not the restriction itself. Optionally, an **error** method can be made to
describe the invalid conversion. describe the invalid conversion.
```cpp ```cpp
template <typename T> template <typename T>
struct even { struct even {
@ -172,6 +226,7 @@ struct even {
return value % 2 == 0; return value % 2 == 0;
} }
// optional
const char* error() const { const char* error() const {
return "number not even"; return "number not even";
} }
@ -182,4 +237,3 @@ struct even {
// returns std::tuple<std::string, int> // returns std::tuple<std::string, int>
auto [name, age] = p.get_next<std::string, even<int>, void>(); auto [name, age] = p.get_next<std::string, even<int>, void>();
```

View File

@ -145,7 +145,11 @@ public:
// method which returns a tuple, returns that type // method which returns a tuple, returns that type
template <typename T, typename... Ts> template <typename T, typename... Ts>
no_void_validator_tup_t<T, Ts...> convert(const split_input& elems) { no_void_validator_tup_t<T, Ts...> convert(const split_input& elems) {
if constexpr (tied_class_v<T, Ts...>) { if constexpr (sizeof...(Ts) == 0 &&
is_instance_of<T, std::tuple>::value) {
return convert_impl(elems, (T*){});
} else if constexpr (tied_class_v<T, Ts...>) {
using arg_ref_tuple = using arg_ref_tuple =
typename std::result_of_t<decltype (&T::tied)(T)>; typename std::result_of_t<decltype (&T::tied)(T)>;
@ -153,10 +157,6 @@ public:
typename apply_trait<std::decay, arg_ref_tuple>::type; typename apply_trait<std::decay, arg_ref_tuple>::type;
return to_object<T>(convert_impl(elems, (arg_tuple*){})); return to_object<T>(convert_impl(elems, (arg_tuple*){}));
} else if constexpr (sizeof...(Ts) == 0 &&
is_instance_of<T, std::tuple>::value) {
return convert_impl(elems, (T*){});
} else { } else {
return convert_impl<T, Ts...>(elems); return convert_impl<T, Ts...>(elems);
} }