format documentation

This commit is contained in:
ado 2021-01-03 21:08:05 +01:00
parent 6a74eb1d3f
commit 2ea6e201d0

138
README.md
View File

@ -6,8 +6,7 @@ Conversion for numeric values taken from [Oliver Schönrock](https://gist.github
Function traits taken from [qt-creator](https://code.woboq.org/qt5/qt-creator/src/libs/utils/functiontraits.h.html) . Function traits taken from [qt-creator](https://code.woboq.org/qt5/qt-creator/src/libs/utils/functiontraits.h.html) .
# Example # Example
Lets say we have a csv file containing students in the Lets say we have a csv file containing students in a given format (NAME,AGE,GRADE) and we want to parse and print all the valid values:
following format <name,age,grade>:
``` ```
$ cat students.csv $ cat students.csv
@ -38,9 +37,7 @@ int main() {
return 0; return 0;
} }
``` ```
And if we compile and execute the program we get the following output: And if we compile and execute the program we get the following output:
``` ```
$ ./a.out $ ./a.out
James Bailey 65 2.5 James Bailey 65 2.5
@ -77,29 +74,20 @@ $ make test
# Usage # Usage
## 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 inside the template type list.
As seen above, 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 If a conversion could not be applied, the method would return a tuple of 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.
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 If **get_next** is called with a **tuple** it would behave identically to passing the same tuple parameters to **get_next**:
the same tuple parameters to **get_next**:
```cpp ```cpp
using student = std::tuple<std::string, int, double>; using student = std::tuple<std::string, int, double>;
// returns std::tuple<std::string, int, double> // returns std::tuple<std::string, int, double>
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 *Note, it does not always return a student tuple since the returned tuples parameters may be altered as explained below (no void, no restrictions, ...)*
parameters may be altered as explained below (no void, no restrictions, ...)*
Whole objects can be returned using the **get_object** function which takes the 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:
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;
@ -111,15 +99,13 @@ struct student {
// returns student // returns student
auto student = p.get_object<student, std::string, int, double>(); auto student = p.get_object<student, std::string, int, double>();
``` ```
This works with any object if the constructor could be invoked using the This works with any object if the constructor could be invoked using the template arguments given to **get_object**:
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,
std::string>(); std::string>();
``` ```
And finally, using something I personally like to do, a struct (class) with a **tied** 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.
method witch returns a tuple of references to to the members of the struct.
```cpp ```cpp
struct student { struct student {
std::string name; std::string name;
@ -129,21 +115,16 @@ struct student {
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. 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.
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>();
``` ```
*Note, the order in which the members of the tied method are returned must *Note, the order in which the members of the tied method are returned must match the order of the elements in the csv*
match the order of the elements in the csv*
### Special types ### Special types
Passing **void** makes the parser ignore a column. 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:
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>();
@ -159,8 +140,7 @@ 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 **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:
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>>
@ -169,9 +149,7 @@ 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 always pass)_:
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<double, char>>
auto [name, age, grade] = auto [name, age, grade] =
@ -184,8 +162,7 @@ if(std::holds_alternative<double>(grade)) {
``` ```
### 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
// 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]
@ -193,9 +170,7 @@ values. **ss::ir** (in range) and **ss::ne** (none empty) are one of those:
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) and **ss::oor** (out of range), **ss::lt** (less than), ...(see *restrictions.hpp*):
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>
@ -204,11 +179,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 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.
**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 {
@ -229,9 +200,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** 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. Custom conversion for an enum would look like this:
function needs to be made and you are good to go. 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 };
@ -250,33 +219,22 @@ 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 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.
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 Detailed error messages can be accessed via the **error_msg** method, and to enable them the error mode has to be changed to **error_mode::error_string** using the **set_error_mode** method:
enable them the error mode has to be changed to **error_mode::error_string** using
the **set_error_mode** method:
```cpp ```cpp
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(); 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::error_bool**. 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.
**error_mode::error_bool**. 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
The parser can also be used to effectively parse files whose rows are not The parser can also be used to effectively parse files whose rows are not always in the same format (not a classical csv but still csv-like). A more complicated example would be the best way to demonstrate such a scenario.
always in the same format (not a classical csv but still csv-like).
A more complicated example would be the best way to demonstrate such a scenario.
Supposing we have a file containing different shapes in given formats: Supposing we have a file containing different shapes in given formats:
* circle RADIUS * circle RADIUS
@ -291,10 +249,7 @@ triangle 3 4 5
... ...
``` ```
The delimiter is " ", and the number of columns varies depending on which The delimiter is " ", and the number of columns varies depending on which shape it is. We are required to read the file and to store information (shape and area) of the shapes into a data structure in the same order as they are in the file.
shape it is. We are required to read the file and to store information
(shape and area) of the shapes into a data structure in the same order
as they are in the file.
```cpp ```cpp
ss::parser p{"shapes.txt", " "}; ss::parser p{"shapes.txt", " "};
if (!p.valid()) { if (!p.valid()) {
@ -338,15 +293,9 @@ while (!p.eof()) {
/* do something with the stored shapes */ /* do something with the stored shapes */
/* ... */ /* ... */
``` ```
It is quite hard to make an error this way since most things will be checked It is quite hard to make an error this way since most things will be checked at compile time.
at compile time.
The **try_next** method works in a similar way as **get_next** but returns a **composit** 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. It also returns a **composite**, but in its tuple is the **optional** to the **tuple** of the previous conversions and an **optional** to the **tuple** to the new conversion.
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.
It also returns a **composite**, but in its tuple is the **optional** to the **tuple**
of the previous conversions and an **optional** to the **tuple** to the new conversion.
To fetch the **tuple** from the **composite** the **values** method is used. To fetch the **tuple** from the **composite** the **values** method is used.
The value of the above used conversion would look something like this The value of the above used conversion would look something like this
@ -358,17 +307,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** 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.
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 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:
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>;
@ -392,11 +333,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 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 will be invoked if none previous conversions were successful. The lambda may take no arguments or one argument , a **std::string**, in which the error message is stored if **error_mode** is set to **error_mode::error_string**:
was invoked. The **composite** also has an **on_error** method which accepts a lambda
will be invoked if none previous conversions were successful. The lambda may
take no arguments or one argument , a **std::string**, in which the error message
is stored if **error_mode** is set to **error_mode::error_string**:
```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 */ })
@ -407,20 +344,13 @@ p.try_next<int>()
# Rest of the library # Rest of the library
First of all, *type_traits.hpp* and *function_traits.hpp* contain many handy First of all, *type_traits.hpp* and *function_traits.hpp* contain many handy traits used in the parser. Most of them are operating on tuples of elements and can be utilized in projects.
traits used in the parser. Most of them are operating on tuples of elements
and can be utilized in projects.
## The converter ## The converter
**ss::parser** is used to manipulate on files. It has a builtin file reader, but **ss::parser** is used to manipulate on files. It has a builtin file reader, but the conversions themselves are done using the **ss::converter**.
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 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.
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;
@ -437,19 +367,13 @@ if (c.valid()) {
All special types and restrictions work on the converter too. Error handling is All special types and restrictions work on the converter too. Error handling is
also identical to error handling of the parser. also identical to error handling of the parser.
The converter has also the ability to just split the line, tho it does not 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. It returns an **std::vector** of pairs of pointers, begin and end, each **std::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.
change it (kinda statically), hence the name of the library.
It returns an **std::vector** of pairs of pointers, begin and end,
each **std::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", " ");
auto [s, r] = c.convert<shape, int>(split_line); auto [s, r] = c.convert<shape, int>(split_line);
``` ```
Using the converter is also an easy and fast way to convert single values. Using the converter is also an easy and fast way to convert single values.
```cpp ```cpp
ss::converter c; ss::converter c;
std::string s; std::string s;