feat: Add a parse(FILE *) interface

The fstream classes are notorious for their non-existent error handling.

This adds a C-style fILE * IO (fopen(), etc.) alternative interface, so
that if a user needs reliable error handling, they can use that, albeit
more inconvenient, but more robust approach.
This commit is contained in:
Lukáš Hrázký 2022-06-30 13:53:32 +02:00
parent 594accf9a7
commit bf9c9d620d
3 changed files with 146 additions and 15 deletions

View File

@ -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<std::string>(data, "title") == "TOML Example");
const auto& owner = toml::find(data, "owner");
{
BOOST_TEST(toml::find<std::string>(owner, "name") == "Tom Preston-Werner");
BOOST_TEST(toml::find<std::string>(owner, "organization") == "GitHub");
BOOST_TEST(toml::find<std::string>(owner, "bio") ==
"GitHub Cofounder & CEO\nLikes tater tots and beer.");
BOOST_TEST(toml::find<toml::offset_datetime>(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<std::string>(database, "server") == "192.168.1.1");
const std::vector<int> expected_ports{8001, 8001, 8002};
BOOST_CHECK(toml::find<std::vector<int>>(database, "ports") == expected_ports);
BOOST_TEST(toml::find<int >(database, "connection_max") == 5000);
BOOST_TEST(toml::find<bool>(database, "enabled") == true);
}
const auto& servers = toml::find(data, "servers");
{
toml::table alpha = toml::find<toml::table>(servers, "alpha");
BOOST_TEST(toml::get<std::string>(alpha.at("ip")) == "10.0.0.1");
BOOST_TEST(toml::get<std::string>(alpha.at("dc")) == "eqdc10");
toml::table beta = toml::find<toml::table>(servers, "beta");
BOOST_TEST(toml::get<std::string>(beta.at("ip")) == "10.0.0.2");
BOOST_TEST(toml::get<std::string>(beta.at("dc")) == "eqdc10");
BOOST_TEST(toml::get<std::string>(beta.at("country")) == "\xE4\xB8\xAD\xE5\x9B\xBD");
}
const auto& clients = toml::find(data, "clients");
{
toml::array clients_data = toml::find<toml::array>(clients, "data");
std::vector<std::string> expected_name{"gamma", "delta"};
BOOST_CHECK(toml::get<std::vector<std::string>>(clients_data.at(0)) == expected_name);
std::vector<int> expected_number{1, 2};
BOOST_CHECK(toml::get<std::vector<int>>(clients_data.at(1)) == expected_number);
std::vector<std::string> expected_hosts{"alpha", "omega"};
BOOST_CHECK(toml::find<std::vector<std::string>>(clients, "hosts") == expected_hosts);
}
std::vector<toml::table> products =
toml::find<std::vector<toml::table>>(data, "products");
{
BOOST_TEST(toml::get<std::string>(products.at(0).at("name")) ==
"Hammer");
BOOST_TEST(toml::get<std::int64_t>(products.at(0).at("sku")) ==
738594937);
BOOST_TEST(toml::get<std::string>(products.at(1).at("name")) ==
"Nail");
BOOST_TEST(toml::get<std::int64_t>(products.at(1).at("sku")) ==
284758393);
BOOST_TEST(toml::get<std::string>(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::runtime_error);
}

View File

@ -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:

View File

@ -2377,27 +2377,14 @@ result<Value, std::string> parse_toml_file(location& loc)
return ok(Value(std::move(data), file, comments));
}
} // detail
template<typename Comment = TOML11_DEFAULT_COMMENT_STRATEGY,
template<typename ...> class Table = std::unordered_map,
template<typename ...> class Array = std::vector>
basic_value<Comment, Table, Array>
parse(std::istream& is, std::string fname = "unknown file")
parse(std::vector<char>& letters, const std::string& fname)
{
using value_type = basic_value<Comment, Table, Array>;
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<char> letters(static_cast<std::size_t>(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,12 +2422,70 @@ parse(std::istream& is, std::string fname = "unknown file")
return data.unwrap();
}
} // detail
template<typename Comment = TOML11_DEFAULT_COMMENT_STRATEGY,
template<typename ...> class Table = std::unordered_map,
template<typename ...> class Array = std::vector>
basic_value<Comment, Table, Array>
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<char> letters(static_cast<std::size_t>(fsize));
std::fread(letters.data(), sizeof(char), static_cast<std::size_t>(fsize), file);
return detail::parse<Comment, Table, Array>(letters, fname);
}
template<typename Comment = TOML11_DEFAULT_COMMENT_STRATEGY,
template<typename ...> class Table = std::unordered_map,
template<typename ...> class Array = std::vector>
basic_value<Comment, Table, Array>
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<char> letters(static_cast<std::size_t>(fsize));
is.read(letters.data(), fsize);
return detail::parse<Comment, Table, Array>(letters, fname);
}
template<typename Comment = TOML11_DEFAULT_COMMENT_STRATEGY,
template<typename ...> class Table = std::unordered_map,
template<typename ...> class Array = std::vector>
basic_value<Comment, Table, Array> 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);