diff --git a/README.md b/README.md index a95e522..bfc5459 100644 --- a/README.md +++ b/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) . # Example -Lets say we have a csv file containing students in the -following format : +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: ``` $ cat students.csv @@ -38,9 +37,7 @@ int main() { return 0; } ``` - And if we compile and execute the program we get the following output: - ``` $ ./a.out James Bailey 65 2.5 @@ -77,29 +74,20 @@ $ make test # Usage ## Conversions -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. +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. -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. +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. -If **get_next** is called with a **tuple** it would behave identically to passing -the same tuple parameters to **get_next**: +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; // returns std::tuple auto [name, age, grade] = p.get_next(); ``` -*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 struct student { std::string name; @@ -111,15 +99,13 @@ struct student { // returns student auto student = p.get_object(); ``` -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 // returns std::vector containing 3 elements auto vec = p.get_object, 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. +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; @@ -129,21 +115,16 @@ struct student { 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 // returns student auto s = p.get_next(); ``` -*Note, the order in which the members of the tied method are returned must -match the order of the elements in the csv* +*Note, the order in which the members of the tied method are returned must match the order of the elements in the csv* ### 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 // returns std::tuple auto [name, grade] = p.get_next(); @@ -159,8 +140,7 @@ To ignore a whole row, **ignore_next** could be used, returns **false** if **eof ```cpp 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 // returns std::tuple> @@ -169,9 +149,7 @@ if(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 // returns std::tuple> auto [name, age, grade] = @@ -184,8 +162,7 @@ if(std::holds_alternative(grade)) { ``` ### 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 one of those: ```cpp // ss::ne makes sure that the name is not empty // 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] = p.get_next, int, ss::ir>(); ``` -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 // all ints exept 10 and 20 ss::ax @@ -204,11 +179,7 @@ ss::nx // all values except the range [0, 10] ss::oor ``` -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 template struct even { @@ -229,9 +200,7 @@ auto [name, age] = p.get_next, void>(); ``` ## 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. 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. Custom conversion for an enum would look like this: ```cpp 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; } ``` -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 -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: +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: ```cpp void parser::set_error_mode(ss::error_mode); const std::string& parser::error_msg(); bool parser::valid(); bool parser::eof(); ``` -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 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. ## Substitute conversions -The parser can also be used to effectively parse files whose rows are not -always in the same format (not a classical csv but still csv-like). -A more complicated example would be the best way to demonstrate such a scenario. +The parser can also be used to effectively parse files whose rows are not always in the same format (not a classical csv but still csv-like). A more complicated example would be the best way to demonstrate such a scenario. Supposing we have a file containing different shapes in given formats: * circle RADIUS @@ -291,10 +249,7 @@ triangle 3 4 5 ... ``` -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. +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. ```cpp ss::parser p{"shapes.txt", " "}; if (!p.valid()) { @@ -338,15 +293,9 @@ while (!p.eof()) { /* do something with the stored shapes */ /* ... */ ``` -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. -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. +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. To fetch the **tuple** from the **composite** the **values** method is used. The value of the above used conversion would look something like this @@ -358,17 +307,9 @@ std::tuple< std::optional> > ``` -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 // non negative double using udbl = ss::gte; @@ -392,11 +333,7 @@ p.try_next, 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 -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**: +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**: ```cpp p.try_next() .on_error([](const std::string& e) { /* int conversion failed */ }) @@ -407,20 +344,13 @@ p.try_next() # Rest of the library -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. +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. ## 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 ss::converter c; @@ -437,19 +367,13 @@ if (c.valid()) { All special types and restrictions work on the converter too. 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. -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. +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. ```cpp ss::converter c; auto split_line = c.split("circle 10", " "); auto [s, r] = c.convert(split_line); ``` Using the converter is also an easy and fast way to convert single values. - ```cpp ss::converter c; std::string s;