diff --git a/tests/test_parse_file.cpp b/tests/test_parse_file.cpp index c2d3610..b525f98 100644 --- a/tests/test_parse_file.cpp +++ b/tests/test_parse_file.cpp @@ -142,6 +142,75 @@ BOOST_AUTO_TEST_CASE(test_example_stream) } } +BOOST_AUTO_TEST_CASE(test_example_file_pointer) +{ + FILE * file = fopen("toml/tests/example.toml", "rb"); + const auto data = toml::parse(file, "toml/tests/example.toml"); + fclose(file); + + BOOST_TEST(toml::find(data, "title") == "TOML Example"); + const auto& owner = toml::find(data, "owner"); + { + BOOST_TEST(toml::find(owner, "name") == "Tom Preston-Werner"); + BOOST_TEST(toml::find(owner, "organization") == "GitHub"); + BOOST_TEST(toml::find(owner, "bio") == + "GitHub Cofounder & CEO\nLikes tater tots and beer."); + BOOST_TEST(toml::find(owner, "dob") == + toml::offset_datetime(toml::local_date(1979, toml::month_t::May, 27), + toml::local_time(7, 32, 0), toml::time_offset(0, 0))); + } + + const auto& database = toml::find(data, "database"); + { + BOOST_TEST(toml::find(database, "server") == "192.168.1.1"); + const std::vector expected_ports{8001, 8001, 8002}; + BOOST_CHECK(toml::find>(database, "ports") == expected_ports); + BOOST_TEST(toml::find(database, "connection_max") == 5000); + BOOST_TEST(toml::find(database, "enabled") == true); + } + + const auto& servers = toml::find(data, "servers"); + { + toml::table alpha = toml::find(servers, "alpha"); + BOOST_TEST(toml::get(alpha.at("ip")) == "10.0.0.1"); + BOOST_TEST(toml::get(alpha.at("dc")) == "eqdc10"); + + toml::table beta = toml::find(servers, "beta"); + BOOST_TEST(toml::get(beta.at("ip")) == "10.0.0.2"); + BOOST_TEST(toml::get(beta.at("dc")) == "eqdc10"); + BOOST_TEST(toml::get(beta.at("country")) == "\xE4\xB8\xAD\xE5\x9B\xBD"); + } + + const auto& clients = toml::find(data, "clients"); + { + toml::array clients_data = toml::find(clients, "data"); + std::vector expected_name{"gamma", "delta"}; + BOOST_CHECK(toml::get>(clients_data.at(0)) == expected_name); + + std::vector expected_number{1, 2}; + BOOST_CHECK(toml::get>(clients_data.at(1)) == expected_number); + + std::vector expected_hosts{"alpha", "omega"}; + BOOST_CHECK(toml::find>(clients, "hosts") == expected_hosts); + } + + std::vector products = + toml::find>(data, "products"); + { + BOOST_TEST(toml::get(products.at(0).at("name")) == + "Hammer"); + BOOST_TEST(toml::get(products.at(0).at("sku")) == + 738594937); + + BOOST_TEST(toml::get(products.at(1).at("name")) == + "Nail"); + BOOST_TEST(toml::get(products.at(1).at("sku")) == + 284758393); + BOOST_TEST(toml::get(products.at(1).at("color")) == + "gray"); + } +} + BOOST_AUTO_TEST_CASE(test_fruit) { const auto data = toml::parse("toml/tests/fruit.toml"); @@ -959,3 +1028,8 @@ BOOST_AUTO_TEST_CASE(test_parse_function_compiles) BOOST_TEST_MESSAGE("path"); #endif } + +BOOST_AUTO_TEST_CASE(test_parse_nonexistent_file) +{ + BOOST_CHECK_THROW(toml::parse("nonexistent.toml"), std::ios_base::failure); +} diff --git a/toml/exception.hpp b/toml/exception.hpp index c64651d..c219216 100644 --- a/toml/exception.hpp +++ b/toml/exception.hpp @@ -10,6 +10,18 @@ namespace toml { +struct file_io_error : public std::runtime_error +{ + public: + file_io_error(int errnum, const std::string& msg, const std::string& fname) + : std::runtime_error(msg + " \"" + fname + "\": " + std::strerror(errnum)), + errno_(errnum) + {} + int get_errno() {return errno_;} + private: + int errno_; +}; + struct exception : public std::exception { public: diff --git a/toml/lexer.hpp b/toml/lexer.hpp index a238d1c..2a1ff2d 100644 --- a/toml/lexer.hpp +++ b/toml/lexer.hpp @@ -5,7 +5,6 @@ #include #include #include -#include #include "combinator.hpp" diff --git a/toml/parser.hpp b/toml/parser.hpp index fe952bf..ce93554 100644 --- a/toml/parser.hpp +++ b/toml/parser.hpp @@ -2377,27 +2377,14 @@ result parse_toml_file(location& loc) return ok(Value(std::move(data), file, comments)); } -} // detail - template class Table = std::unordered_map, template class Array = std::vector> basic_value -parse(std::istream& is, std::string fname = "unknown file") +parse(std::vector& letters, const std::string& fname) { using value_type = basic_value; - const auto beg = is.tellg(); - is.seekg(0, std::ios::end); - const auto end = is.tellg(); - const auto fsize = end - beg; - is.seekg(beg); - - // read whole file as a sequence of char - assert(fsize >= 0); - std::vector letters(static_cast(fsize)); - is.read(letters.data(), fsize); - // append LF. // Although TOML does not require LF at the EOF, to make parsing logic // simpler, we "normalize" the content by adding LF if it does not exist. @@ -2435,16 +2422,75 @@ parse(std::istream& is, std::string fname = "unknown file") return data.unwrap(); } +} // detail + +template class Table = std::unordered_map, + template class Array = std::vector> +basic_value +parse(FILE * file, const std::string& fname) +{ + const long beg = std::ftell(file); + if (beg == -1l) { + throw file_io_error(errno, "Failed to access", fname); + } + + int res = std::fseek(file, 0, SEEK_END); + if (res != 0) { + throw file_io_error(errno, "Failed to seek", fname); + } + + const long end = std::ftell(file); + if (end == -1l) { + throw file_io_error(errno, "Failed to access", fname); + } + + const auto fsize = end - beg; + + res = std::fseek(file, beg, SEEK_SET); + if (res != 0) { + throw file_io_error(errno, "Failed to seek", fname); + } + + // read whole file as a sequence of char + assert(fsize >= 0); + std::vector letters(static_cast(fsize)); + std::fread(letters.data(), sizeof(char), static_cast(fsize), file); + + return detail::parse(letters, fname); +} + +template class Table = std::unordered_map, + template class Array = std::vector> +basic_value +parse(std::istream& is, std::string fname = "unknown file") +{ + const auto beg = is.tellg(); + is.seekg(0, std::ios::end); + const auto end = is.tellg(); + const auto fsize = end - beg; + is.seekg(beg); + + // read whole file as a sequence of char + assert(fsize >= 0); + std::vector letters(static_cast(fsize)); + is.read(letters.data(), fsize); + + return detail::parse(letters, fname); +} + template class Table = std::unordered_map, template class Array = std::vector> basic_value parse(std::string fname) { - std::ifstream ifs(fname.c_str(), std::ios_base::binary); + std::ifstream ifs(fname, std::ios_base::binary); if(!ifs.good()) { - throw std::runtime_error("toml::parse: file open error -> " + fname); + throw std::ios_base::failure("toml::parse: Error opening file \"" + fname + "\""); } + ifs.exceptions(std::ifstream::failbit | std::ifstream::badbit); return parse(ifs, std::move(fname)); } @@ -2471,13 +2517,7 @@ template class Array = std::vector> basic_value parse(const std::filesystem::path& fpath) { - std::ifstream ifs(fpath, std::ios_base::binary); - if(!ifs.good()) - { - throw std::runtime_error("toml::parse: file open error -> " + - fpath.string()); - } - return parse(ifs, fpath.string()); + return parse(fpath.string()); } #endif // TOML11_HAS_STD_FILESYSTEM