{"id":26474194,"url":"https://github.com/mrizaln/cppread","last_synced_at":"2025-03-19T22:45:23.109Z","repository":{"id":260942177,"uuid":"850611563","full_name":"mrizaln/cppread","owner":"mrizaln","description":"Simple console input library written in C++20","archived":false,"fork":false,"pushed_at":"2024-11-20T17:30:05.000Z","size":84,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-11-20T18:30:55.633Z","etag":null,"topics":["cpp","input","io"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mrizaln.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-09-01T09:28:10.000Z","updated_at":"2024-11-20T17:30:38.000Z","dependencies_parsed_at":null,"dependency_job_id":"e2de8def-beb0-4da6-a086-096187173c11","html_url":"https://github.com/mrizaln/cppread","commit_stats":null,"previous_names":["mrizaln/cppread"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrizaln%2Fcppread","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrizaln%2Fcppread/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrizaln%2Fcppread/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrizaln%2Fcppread/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mrizaln","download_url":"https://codeload.github.com/mrizaln/cppread/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244520022,"owners_count":20465624,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cpp","input","io"],"created_at":"2025-03-19T22:45:22.483Z","updated_at":"2025-03-19T22:45:23.103Z","avatar_url":"https://github.com/mrizaln.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# cppread\n\nSimple console input library written in C++20.\n\n## Motivation\n\nUsing `std::cin` is just generally annoying and painful. Furthermore the error handling is quite awkward at best.\n\n```cpp\n// ...\n\nint main() {\n    // C++ standard library gives us this:\n    {\n        int value;                  // garbage value, uhh... not good\n        std::cout \u003c\u003c \"prompt: \";\n        std::cin \u003e\u003e value;\n\n        if (not std::cin.good()) {                          // check all potential errors\n\n            // handle extraction failure...\n\n            // these are necessary steps, and I might have forgot to do these!\n            std::cin.clear();\n            std::cin.ignore(std::numeric_limits\u003cstd::streamsize\u003e::max(), '\\n');\n\n            // std::cin should be usable again after two steps above...\n        }\n\n        // finally, using the value\n    }\n\n    // why can't we just do something like this instead:\n    {\n        auto value = read\u003cint\u003e(\"prompt: \");\n        if (not value) {                                    // check all potential errors\n            // handle parse/extraction failure...\n        }\n\n        // use the value\n    }\n}\n\n// ...\n```\n\nThis library is intended to be used in my personal projects if I ever have the need to get inputs from `stdin`, but if you feel this library fits your need, feel free to use it. These are the features this library offer:\n\n- Simple function-based input instead of stream-based input of `std::cin`.\n- Line based input: each read consume an entire line of the `stdin` (using `getline` on linux (glibc) else `fgets`; define/undef `CPPREAD_ENABLE_GETLINE` to override).\n- Improved error handling: using custom type that wraps a variant: `cppread::Result\u003cT\u003e`.\n- Exception-free: no exception thrown from `cppread::read` functions.\n- Buffered or non-buffered read, it's your choice.\n- `const` compatible: you can assign the result of `cppread::read` to a `const` variable easily.\n- Built-in parser for fundamental types (using `std::from_chars`; `bool` has separate implementation) (see the implementation [here](./include/cppread/detail/default_parser.hpp)).\n- Allow overriding default parser via `cppread::CustomParser` specialization.\n- Allow extension for custom type via specialization of `cppread::CustomParser`.\n\n## Example\n\n\u003e See [example](./example/source/main.cpp) for more examples\n\n### Simple read\n\n\u003e Error handling are ignored in order to be succinct, see the next section to have a feel on how to handle errors.\n\n```cpp\n#include \u003ccppread/read.hpp\u003e\n\n#include \u003ciostream\u003e\n#include \u003cformat\u003e\n\n// please excuse my use of macro :)\n#define println(...) std::cout \u003c\u003c std::format(__VA_ARGS__) \u003c\u003c '\\n'\n\nusing cppread::read;\n\nint main()\ntry {\n    // single value read\n    //-------------------\n    auto result = read\u003cint\u003e(\"Please enter an integer: \");\n    if (result) {\n        println(\"value: {}\", result.value());                       // retrieve contained value\n    } else {\n        println(\"cppread::Error: '{}'\", toString(result.error()));  // get human readable error description (ADL in effect here)\n    }\n\n    // multiple values read\n    //----------------------\n    auto result2 = read\u003cint, char, float\u003e(\"[int char float]: \");\n    auto [i, c, f] = std::move(result2).value();                    // returned value is tuple, you can use structured binding\n    println(\"int: {} | char: {} | float: {}\", i, c, f);\n\n    // read a whole line of string\n    //-----------------------------\n    auto string = read(\"Enter anything: \").value();                 // `value()` will throw if error was returned instead\n    println(\"anything: {}\", string);\n\n    // use custom delimiter instead of space for multiple values read\n    //----------------------------------------------------------------\n    auto [ii, ff, cc] = read\u003cint, float, char\u003e(\"[int/float/char]: \", '/').value();\n    println(\"result [{}/{}/{}]\", ii, ff, cc);\n\n} catch (std::exception\u0026 e) {\n    println(\"exception: {}\", e.what());\n}\n```\n\n\u003e Try inputting these, each line for each prompt\n\u003e\n\u003e ```\n\u003e 42\n\u003e 12938 H 3.14159\n\u003e OV;9EC6V\")6o7b t6r6R P78v6bp986 vt2\\32487./`97ecoP786[BVTIC;O9p 7p9h6vtV8VR8O7 IBYO ROUN]\"\n\u003e 100/0.001/D\n\u003e ```\n\n### Repeated read\n\nFor example, if you want to get an `int` with value greater than `42`, you might do something like this:\n\n```cpp\n#include \u003ccppread/read.hpp\u003e\n#include \u003ciostream\u003e\n\nusing cppread::read, cppread::Error;\n\n// wrap the read into a function/lambda so that the returned value can be const\nauto readRepeat() {\n    while (true) {\n        auto result = read\u003cint\u003e(\"Please enter an integer greater than 42: \");\n        if (not result) {\n            switch (result.error()) {\n            case Error::InvalidInput:\n            case Error::OutOfRange:\n                std::cout \u003c\u003c \"Invalid input, please try again\\n\";\n                continue;\n            case Error::EndOfFile:\n            case Error::Unknown:\n                std::cout \u003c\u003c \"stdin got into an unrecoverable error state!\\n\";\n                throw std::runtime_error{ toString(result.error()).data() };\n            }\n        }\n\n        if (result.value() \u003c= 42) {\n            std::cout \u003c\u003c \"Inputted value is less than or equal to 42, please try again\";\n            continue;\n        }\n\n        return result.value();\n    }\n}\n\nint main() {\n    const int value = readRepeat();\n\n    // consume the value...\n}\n```\n\n### Custom type parser\n\nYou can parse your own type by specializing `cppread::CustomParser` struct. The shape of the struct must conform to `cppread::CustomParseable` concept.\n\n```cpp\n#include \u003ccppread/read.hpp\u003e\n#include \u003ccppread/parser.hpp\u003e    // cppread::CustomParser, cppread::CustomParseable, cppread::Parseable\n\nstruct Color\n{\n    float m_r;\n    float m_g;\n    float m_b;\n};\n\ntemplate \u003c\u003e\nstruct cppread::CustomParser\u003cColor\u003e\n{\n    Result\u003cColor\u003e parse(Str str) const noexcept\n    {\n        // parse string with the shape: `Color { \u003cr\u003e \u003cg\u003e \u003cb\u003e }`\n        //                               0     1 2   3   4   5\n\n        // use cppread's split (repeated delimiter counted as one)\n        auto parts = cppread::util::split\u003c6\u003e(str, ' ');\n        if (not parts) {\n            return Error::InvalidInput;\n        }\n\n        if (parts-\u003eat(0) != \"Color\" || parts-\u003eat(1) != \"{\" || parts-\u003eat(5) != \"}\") {\n            return Error::InvalidInput;\n        }\n\n        // parse the underlying type using default parser\n        auto r = cppread::parse\u003cfloat\u003e(parts-\u003eat(2));\n        auto g = cppread::parse\u003cfloat\u003e(parts-\u003eat(3));\n        auto b = cppread::parse\u003cfloat\u003e(parts-\u003eat(4));\n\n        if (not r || not g || not b) {\n            return Error::InvalidInput;\n        }\n\n        return Color{ r.value(), g.value(), b.value() };\n    }\n};\n\nstatic_assert(cppread::CustomParseable\u003cColor\u003e);\nstatic_assert(cppread::Parseable\u003cColor\u003e);\n\nint main() {\n    // the delimiter set to '\\n' since the Color parser reads a substring that contains space,\n    // effectively read the entire line\n    auto result = cppread::read\u003cColor\u003e(\"input color: \", '\\n');\n\n    // use the result ...\n}\n```\n\n\u003e See this [example](./example/source/custom_type.cpp) for overriding default parser\n\n## Documentation\n\nThis library is a simple library (about 500 LOC, measured using `cloc`), so a dedicated documentation is not necessary. You can read the headers directly to see the documentation (Doxygen format).\n\n## Benchmark\n\n### time\n\n\u003e - Benchmark performed on Intel(R) Core(TM) i5-10500H (12 threads) with the frequency locked at 2.5GHz.\n\u003e - `hyperfine` is used with parameter `--warmup 3`.\n\u003e - The benchmark involves parsing about 625k lines of 4 `(float | int)` separated by space (generated using [this script](example/random_gen.sh); `nan` removed).\n\u003e - The benchmark code is [here](example/source/bench.cpp).\n\n|                                      | `615217 4-floats`     | `625000 4-ints`     |\n| ------------------------------------ | --------------------- | ------------------- |\n| `std::cin` (unsynced)                | `1.155  s ± 0.003  s` | `298.6 ms ± 2.8 ms` |\n| `cppread::read` (getline)            | `261.3 ms ± 1.8   ms` | `163.7 ms ± 1.7 ms` |\n| `cppread::read` (fgets)              | `287.0 ms ± 1.5   ms` | `183.9 ms ± 1.3 ms` |\n| `cppread::BufReader::read` (getline) | `253.4 ms ± 1.8   ms` | `145.7 ms ± 1.1 ms` |\n| `cppread::BufReader::read` (fgets)   | `270.8 ms ± 2.2   ms` | `160.7 ms ± 1.1 ms` |\n\nAs you can see, this library is generally faster than `std::cin` (unsynced) and can gain up to 4.4x speedup.\n\n### allocation\n\n\u003e - Measured using `memusage`.\n\u003e - Allocations not done by the library is deducted from the total number.\n\n|                                      | calls to malloc/new |\n| ------------------------------------ | ------------------: |\n| `std::cin` (unsynced)                |           `2460901` |\n| `cppread::read` (getline)            |            `615219` |\n| `cppread::read` (fgets)              |            `615219` |\n| `cppread::BufReader::read` (getline) |                 `7` |\n| `cppread::BufReader::read` (fgets)   |                 `7` |\n\nSince `cppread::BufReader` retains its buffer for its lifetime (and it grows as needed), allocation only happen few times at the start. This can help reduce memory fragmentation.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmrizaln%2Fcppread","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmrizaln%2Fcppread","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmrizaln%2Fcppread/lists"}