mirror of
https://github.com/red0124/ssp.git
synced 2025-01-23 04:55:20 +01:00
format documentation
This commit is contained in:
parent
6a74eb1d3f
commit
2ea6e201d0
138
README.md
138
README.md
@ -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;
|
||||||
|
Loading…
Reference in New Issue
Block a user