From f105402d48560f2a43f5a8ba0f114193900d17bb Mon Sep 17 00:00:00 2001 From: Cyrus Harrison Date: Thu, 14 Jan 2021 15:57:04 -0800 Subject: [PATCH] add conduit::utils::format --- CHANGELOG.md | 1 + src/libs/conduit/conduit_utils.cpp | 476 ++++++++++++++++++++++++++ src/libs/conduit/conduit_utils.hpp | 14 +- src/tests/conduit/t_conduit_utils.cpp | 265 +++++++++++--- 4 files changed, 698 insertions(+), 58 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18e3414c3..5ba533e8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project aspires to adhere to [Semantic Versioning](https://semver.org/s - Added a builtin sandboxed header-only version of fmt. The namespace and directory paths were changed to `conduit_fmt` to avoid potential symbol collisions with other codes using fmt. Downstream software can use by including `conduit_fmt/conduit_fmt.h`. - Added support for using C++11 initializer lists to set Node and DataArray values from numeric arrays. See C++ tutorial docs (https://llnl-conduit.readthedocs.io/en/latest/tutorial_cpp_numeric.html#c-11-initializer-lists) for more details. - Added a Node::describe() method. This method creates a new node that mirrors the current Node, however each leaf is replaced by summary stats and a truncated display of the values. For use cases with large leaves, printing the describe() output Node is much more helpful for debugging and understanding vs wall of text from other to_string() methods. +- Added conduit::utils::format methods. These methods use fmt to format strings that include fmt style patterns. The formatting argments are passed as a conduit::Node tree. The `args` case allows named arguments (args passed as object) or ordered args (args passed as list). The `maps` case also supports named or ordered args and works in conjunction with a `map_index`. The `map_index` is used to fetch a value from an array, or list of strings, which is then passed to fmt. The `maps` style of indexed indirection supports generating path strings for non-trivial domain partition mappings in Blueprint. #### Relay diff --git a/src/libs/conduit/conduit_utils.cpp b/src/libs/conduit/conduit_utils.cpp index c1b8b5ad7..8b522acf9 100644 --- a/src/libs/conduit/conduit_utils.cpp +++ b/src/libs/conduit/conduit_utils.cpp @@ -50,6 +50,9 @@ static const std::string file_path_sep_string(CONDUIT_UTILS_FILE_PATH_SEPARATOR); +#include "conduit.hpp" +#include "conduit_fmt/conduit_fmt.h" + //----------------------------------------------------------------------------- // -- libb64 includes -- @@ -963,6 +966,479 @@ float64_to_string(float64 value) return res; } +//----------------------------------------------------------------------------- +/// fmt style string formatting helpers +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +std::string +format(const std::string &pattern, + const conduit::Node &args) +{ + if( !args.dtype().is_object() && + !args.dtype().is_list()) + { + CONDUIT_ERROR("conduit::utils::format args Node must be " + << " an `object`, or `list`"); + } + + // if we have an object, we used named args for fmt + // if we have an list, we don't use named args for fmt + bool is_obj = args.dtype().is_object(); + + conduit_fmt::dynamic_format_arg_store store; + conduit::NodeConstIterator itr = args.children(); + + while(itr.has_next()) + { + const conduit::Node &curr = itr.next(); + switch(curr.dtype().id()) + { + /* ints */ + case conduit::DataType::INT8_ID: + { + int8 val = curr.as_int8(); + if(is_obj) + { + store.push_back(conduit_fmt::arg(itr.name().c_str(), + val)); + } + else + { + store.push_back(val); + } + break; + } + case conduit::DataType::INT16_ID: + { + int16 val = curr.as_int16(); + if(is_obj) + { + store.push_back(conduit_fmt::arg(itr.name().c_str(), + val)); + } + else + { + store.push_back(val); + } + break; + } + case conduit::DataType::INT32_ID: + { + int32 val = curr.as_int32(); + if(is_obj) + { + store.push_back(conduit_fmt::arg(itr.name().c_str(), + val)); + } + else + { + store.push_back(val); + } + break; + } + case conduit::DataType::INT64_ID: + { + int64 val = curr.as_int64(); + if(is_obj) + { + store.push_back(conduit_fmt::arg(itr.name().c_str(), + val)); + } + else + { + store.push_back(val); + } + break; + } + /* uints */ + case conduit::DataType::UINT8_ID: + { + uint8 val = curr.as_uint8(); + if(is_obj) + { + store.push_back(conduit_fmt::arg(itr.name().c_str(), + val)); + } + else + { + store.push_back(val); + } + break; + } + case conduit::DataType::UINT16_ID: + { + uint16 val = curr.as_uint16(); + if(is_obj) + { + store.push_back(conduit_fmt::arg(itr.name().c_str(), + val)); + } + else + { + store.push_back(val); + } + break; + } + case conduit::DataType::UINT32_ID: + { + uint32 val = curr.as_uint32(); + if(is_obj) + { + store.push_back(conduit_fmt::arg(itr.name().c_str(), + val)); + } + else + { + store.push_back(val); + } + break; + } + case conduit::DataType::UINT64_ID: + { + uint64 val = curr.as_uint64(); + if(is_obj) + { + store.push_back(conduit_fmt::arg(itr.name().c_str(), + val)); + } + else + { + store.push_back(val); + } + break; + } + /* floats */ + case conduit::DataType::FLOAT32_ID: + { + float32 val = curr.as_float32(); + if(is_obj) + { + store.push_back(conduit_fmt::arg(itr.name().c_str(), + val)); + } + else + { + store.push_back(val); + } + break; + } + case conduit::DataType::FLOAT64_ID: + { + float64 val = curr.as_float64(); + if(is_obj) + { + store.push_back(conduit_fmt::arg(itr.name().c_str(), + val)); + } + else + { + store.push_back(val); + } + break; + } + // string case + case conduit::DataType::CHAR8_STR_ID: + { + std::string val = curr.as_string(); + if(is_obj) + { + store.push_back(conduit_fmt::arg(itr.name().c_str(), + val)); + } + else + { + store.push_back(val); + } + + break; + } + default: + { + // ERROR -- list, object, or empty + CONDUIT_ERROR("conduit::utils::format does not support" + << " `object`, `list`, or `empty` Nodes" + << " as arguments." + << "'" << itr.name() << "' is a " + << curr.dtype().name() << "."); + } + } + } + + std::string res = ""; + + try + { + res = conduit_fmt::vformat(pattern,store); + } + catch(const std::runtime_error& re) + { + CONDUIT_ERROR("conduit::utils::format error: " << re.what()); + } + + return res; +} + +//----------------------------------------------------------------------------- +std::string +format(const std::string &pattern, + const conduit::Node &maps, + index_t map_index) +{ + // neg bounds check + if(map_index < 0) + { + CONDUIT_ERROR("conduit::utils::format map_index must be positive " + << " (map_index = " << map_index << ")"); + } + + if( !maps.dtype().is_object() && + !maps.dtype().is_list()) + { + CONDUIT_ERROR("conduit::utils::format maps Node must be " + << " an `object`, or `list`"); + + } + + // if we have an object, we used named args for fmt + // if we have an list, we don't use named args for fmt + bool is_obj = maps.dtype().is_object(); + + conduit_fmt::dynamic_format_arg_store store; + conduit::NodeConstIterator itr = maps.children(); + while(itr.has_next()) + { + const conduit::Node &curr = itr.next(); + // map bounds checks + if(curr.dtype().is_list()) + { + if(map_index >= curr.number_of_children()) + { + CONDUIT_ERROR("conduit::utils::format map_index " + << "(value = " << map_index << ")" + << " for '" << itr.name() << "'" + << " list map entry " + << " is out of bounds." + << " Number of children = " + << curr.number_of_children() + << " valid range is [0," + << curr.number_of_children() << ")."); + } + } + else if(curr.dtype().is_number()) + { + if(map_index >= curr.dtype().number_of_elements()) + { + CONDUIT_ERROR("conduit::utils::format map_index " + << "(value = " << map_index << ")" + << " for '" << itr.name() << "'" + << " array map entry " + << " is out of bounds." + << " Number of elements = " + << curr.dtype().number_of_elements() + << " valid range is [0," + << curr.dtype().number_of_elements() << ")."); + } + } + + switch(curr.dtype().id()) + { + /* ints */ + case conduit::DataType::INT8_ID: + { + int8 val = curr.as_int8_ptr()[map_index]; + if(is_obj) + { + store.push_back(conduit_fmt::arg(itr.name().c_str(), + val)); + } + else + { + store.push_back(val); + } + break; + } + case conduit::DataType::INT16_ID: + { + int16 val = curr.as_int16_ptr()[map_index]; + if(is_obj) + { + store.push_back(conduit_fmt::arg(itr.name().c_str(), + val)); + } + else + { + store.push_back(val); + } + break; + } + case conduit::DataType::INT32_ID: + { + int32 val = curr.as_int32_ptr()[map_index]; + if(is_obj) + { + store.push_back(conduit_fmt::arg(itr.name().c_str(), + val)); + } + else + { + store.push_back(val); + } + break; + } + case conduit::DataType::INT64_ID: + { + int64 val = curr.as_int64_ptr()[map_index]; + if(is_obj) + { + store.push_back(conduit_fmt::arg(itr.name().c_str(), + val)); + } + else + { + store.push_back(val); + } + break; + } + /* uints */ + case conduit::DataType::UINT8_ID: + { + uint8 val = curr.as_uint8_ptr()[map_index]; + if(is_obj) + { + store.push_back(conduit_fmt::arg(itr.name().c_str(), + val)); + } + else + { + store.push_back(val); + } + break; + } + case conduit::DataType::UINT16_ID: + { + uint16 val = curr.as_uint16_ptr()[map_index]; + if(is_obj) + { + store.push_back(conduit_fmt::arg(itr.name().c_str(), + val)); + } + else + { + store.push_back(val); + } + break; + } + case conduit::DataType::UINT32_ID: + { + uint32 val = curr.as_uint32_ptr()[map_index]; + if(is_obj) + { + store.push_back(conduit_fmt::arg(itr.name().c_str(), + val)); + } + else + { + store.push_back(val); + } + break; + } + case conduit::DataType::UINT64_ID: + { + uint64 val = curr.as_uint64_ptr()[map_index]; + if(is_obj) + { + store.push_back(conduit_fmt::arg(itr.name().c_str(), + val)); + } + else + { + store.push_back(val); + } + break; + } + /* floats */ + case conduit::DataType::FLOAT32_ID: + { + float32 val = curr.as_float32_ptr()[map_index]; + if(is_obj) + { + store.push_back(conduit_fmt::arg(itr.name().c_str(), + val)); + } + else + { + store.push_back(val); + } + break; + } + case conduit::DataType::FLOAT64_ID: + { + float64 val = curr.as_float64_ptr()[map_index]; + if(is_obj) + { + store.push_back(conduit_fmt::arg(itr.name().c_str(), + val)); + } + else + { + store.push_back(val); + } + break; + } + // support lists of strings ONLY ... + case conduit::DataType::LIST_ID: + { + const Node &lst_ent = curr[map_index]; + if(!lst_ent.dtype().is_string()) + { + CONDUIT_ERROR("conduit::utils::format (maps) only supports " + << " the list maps case for strings." + << "'" << itr.name() << "' entry at index " + << map_index << " is a " + << lst_ent.dtype().name() << "."); + } + + std::string val = lst_ent.as_string(); + if(is_obj) + { + store.push_back(conduit_fmt::arg(itr.name().c_str(), + val)); + } + else + { + store.push_back(val); + } + + break; + } + default: + { + // ERROR -- object, string, or empty + CONDUIT_ERROR("conduit::utils::format (maps) does not support" + << " `object`, `string, or `empty` Nodes" + << " as arguments." + << "'" << itr.name() << "' is a " + << curr.dtype().name() << "."); + } + } + } + + std::string res = ""; + + try + { + res = conduit_fmt::vformat(pattern,store); + } + catch(const std::runtime_error& re) + { + CONDUIT_ERROR("conduit::utils::format error: " << re.what()); + } + + return res; +} + + //----------------------------------------------------------------------------- // String hash functions //----------------------------------------------------------------------------- diff --git a/src/libs/conduit/conduit_utils.hpp b/src/libs/conduit/conduit_utils.hpp index 69887e806..c3ed26015 100644 --- a/src/libs/conduit/conduit_utils.hpp +++ b/src/libs/conduit/conduit_utils.hpp @@ -26,7 +26,6 @@ //----------------------------------------------------------------------------- #include "conduit_core.hpp" - //----------------------------------------------------------------------------- // /// The CONDUIT_INFO macro is the primary mechanism used to log basic messages. @@ -157,6 +156,9 @@ namespace conduit { +// fwd declare Node +class Node; + //----------------------------------------------------------------------------- // -- begin conduit::utils -- //----------------------------------------------------------------------------- @@ -359,6 +361,16 @@ namespace utils std::string CONDUIT_API unescape_special_chars(const std::string &input); +//----------------------------------------------------------------------------- +/// fmt style string formatting helpers +//----------------------------------------------------------------------------- + + std::string CONDUIT_API format(const std::string &s, + const conduit::Node &args); + + std::string CONDUIT_API format(const std::string &s, + const conduit::Node &maps, + index_t map_index); //----------------------------------------------------------------------------- /// Base64 Encoding of Buffers diff --git a/src/tests/conduit/t_conduit_utils.cpp b/src/tests/conduit/t_conduit_utils.cpp index a22686609..976c3baae 100644 --- a/src/tests/conduit/t_conduit_utils.cpp +++ b/src/tests/conduit/t_conduit_utils.cpp @@ -23,10 +23,9 @@ using namespace conduit; bool info_occured = false; bool warning_occured = false; bool error_occured = false; - //----------------------------------------------------------------------------- -void +void print_msg(const std::string &msg, const std::string &file, int line) @@ -37,7 +36,7 @@ print_msg(const std::string &msg, } //----------------------------------------------------------------------------- -void +void my_info_handler(const std::string &msg, const std::string &file, int line) @@ -48,7 +47,7 @@ my_info_handler(const std::string &msg, //----------------------------------------------------------------------------- -void +void my_warning_handler(const std::string &msg, const std::string &file, int line) @@ -58,7 +57,7 @@ my_warning_handler(const std::string &msg, } //----------------------------------------------------------------------------- -void +void my_error_handler(const std::string &msg, const std::string &file, int line) @@ -73,7 +72,7 @@ TEST(conduit_utils, error_constructors) conduit::Error e("mymessage","myfile",10); CONDUIT_INFO(e.message()); CONDUIT_INFO(e.what()); - + try { utils::handle_warning("ERROR!",__FILE__,__LINE__); @@ -83,7 +82,7 @@ TEST(conduit_utils, error_constructors) conduit::Error ecpy(e); CONDUIT_INFO(ecpy.message()); } - + try { utils::handle_warning("ERROR!",__FILE__,__LINE__); @@ -103,13 +102,13 @@ TEST(conduit_utils, override_info) utils::handle_info("INFO!",__FILE__,__LINE__); EXPECT_FALSE(info_occured); - + conduit::utils::set_info_handler(my_info_handler); utils::handle_info("INFO!",__FILE__,__LINE__); EXPECT_TRUE(info_occured); - + conduit::utils::set_info_handler(conduit::utils::default_info_handler); - + utils::handle_info("INFO!",__FILE__,__LINE__); } @@ -119,14 +118,14 @@ TEST(conduit_utils, override_warning) { EXPECT_THROW(utils::handle_warning("WARNING!",__FILE__,__LINE__), conduit::Error); - + EXPECT_FALSE(warning_occured); conduit::utils::set_warning_handler(my_warning_handler); utils::handle_warning("WARNING!",__FILE__,__LINE__); EXPECT_TRUE(warning_occured); - + conduit::utils::set_warning_handler(conduit::utils::default_warning_handler); - + EXPECT_THROW(utils::handle_warning("WARNING!",__FILE__,__LINE__), conduit::Error); } @@ -141,14 +140,13 @@ TEST(conduit_utils, override_error) conduit::utils::set_error_handler(my_error_handler); utils::handle_error("ERROR!",__FILE__,__LINE__); EXPECT_TRUE(error_occured); - + conduit::utils::set_error_handler(conduit::utils::default_error_handler); - + EXPECT_THROW(utils::handle_warning("ERROR!",__FILE__,__LINE__), conduit::Error); - -} +} //----------------------------------------------------------------------------- TEST(conduit_utils, escape_special_chars) @@ -177,9 +175,9 @@ TEST(conduit_utils, escape_special_chars) TEST(conduit_utils, float64_to_string) { float64 v = 10.0; - + EXPECT_EQ("10.0",utils::float64_to_string(v)); - + v = 10000000000000000; EXPECT_EQ("1e+16",utils::float64_to_string(v)); @@ -206,7 +204,7 @@ TEST(conduit_utils, is_dir) { EXPECT_TRUE(utils::is_directory(CONDUIT_T_SRC_DIR)); EXPECT_TRUE(utils::is_directory(CONDUIT_T_BIN_DIR)); - + EXPECT_FALSE(utils::is_directory("asdasdasdasd")); } @@ -221,10 +219,10 @@ TEST(conduit_utils, is_file) tf_path = utils::join_file_path(tf_path,"t_conduit_utils.cpp"); EXPECT_TRUE(utils::is_file(tf_path)); - + EXPECT_FALSE(utils::is_file(CONDUIT_T_SRC_DIR)); EXPECT_FALSE(utils::is_file(CONDUIT_T_BIN_DIR)); - + EXPECT_FALSE(utils::is_file("asdasdasdasd")); } @@ -234,15 +232,15 @@ TEST(conduit_utils, is_file) TEST(conduit_utils, remove_file) { std::ofstream ofs; - + ofs.open("t_remove_file.txt"); ofs << "here" << std::endl; ofs.close(); EXPECT_TRUE(utils::is_file("t_remove_file.txt")); - + utils::remove_file("t_remove_file.txt"); - + EXPECT_FALSE(utils::is_file("t_remove_file.txt")); } @@ -265,38 +263,38 @@ TEST(conduit_utils, base64_enc_dec) n_src["a"].set_int32(10); n_src["b"].set_int32(20); n_src["c"].set_int32(30); - + // we need compact data for base64 Node n; n_src.compact_to(n); - + // use libb64 to encode the data index_t nbytes = n.schema().total_strided_bytes(); Node bb64_data; index_t enc_buff_size = utils::base64_encode_buffer_size(nbytes); - + bb64_data.set(DataType::char8_str(enc_buff_size)); - + const char *src_ptr = (const char*)n.data_ptr(); char *dest_ptr = (char*)bb64_data.data_ptr(); memset(dest_ptr,0,(size_t)enc_buff_size); - + utils::base64_encode(src_ptr,nbytes,dest_ptr); index_t dec_buff_size = utils::base64_decode_buffer_size(enc_buff_size); // use libb64 to decode the data - + // decode buffer Node bb64_decode; bb64_decode.set(DataType::char8_str(dec_buff_size)); char *b64_decode_ptr = (char*)bb64_decode.data_ptr(); memset(b64_decode_ptr,0,(size_t)dec_buff_size); - + utils::base64_decode(bb64_data.as_char8_str(), enc_buff_size, b64_decode_ptr); - + // apply schema Node n_res(n.schema(),b64_decode_ptr,false); @@ -311,11 +309,11 @@ TEST(conduit_utils, dir_create_and_remove_tests) { std::string test_dir = utils::join_file_path(CONDUIT_T_BIN_DIR, "tout_dir_create_test"); - + CONDUIT_INFO("test creating and removing dir: " << test_dir); - + EXPECT_FALSE(utils::is_directory(test_dir)); - + EXPECT_TRUE(utils::create_directory(test_dir)); EXPECT_TRUE(utils::is_directory(test_dir)); @@ -336,25 +334,25 @@ TEST(conduit_utils, file_path_split_tests) #else EXPECT_EQ(sep,"/"); #endif - + std::string my_path = "a" + sep + "b" + sep + "c"; std::string my_path_via_join = utils::join_file_path("a","b"); my_path_via_join = utils::join_file_path(my_path_via_join,"c"); - + EXPECT_EQ(my_path,my_path_via_join); std::string curr,next; utils::split_file_path(my_path,curr,next); EXPECT_EQ(curr,"a"); EXPECT_EQ(next,"b" + sep + "c"); - + my_path = next; utils::split_file_path(my_path,curr,next); EXPECT_EQ(curr,"b"); EXPECT_EQ(next,"c"); - + my_path = next; utils::split_file_path(my_path,curr,next); EXPECT_EQ(curr,"c"); @@ -366,12 +364,12 @@ TEST(conduit_utils, file_path_split_tests) utils::rsplit_file_path(my_path,curr,next); EXPECT_EQ(curr,"c"); EXPECT_EQ(next,"a" + sep + "b"); - + my_path = next; utils::rsplit_file_path(my_path,curr,next); EXPECT_EQ(curr,"b"); EXPECT_EQ(next,"a"); - + my_path = next; utils::rsplit_file_path(my_path,curr,next); EXPECT_EQ(curr,"a"); @@ -384,14 +382,14 @@ TEST(conduit_utils, file_path_split_tests) TEST(conduit_utils, join_path_tests) { // note: these test joining conduit paths, not file system paths - - + + std::string res = utils::join_path("","mypath"); EXPECT_EQ(res,"mypath"); - + res = utils::join_path("mypath",""); EXPECT_EQ(res,"mypath"); - + res = utils::join_path("",""); EXPECT_EQ(res,""); @@ -414,10 +412,10 @@ TEST(conduit_utils, split_windows_paths) utils::split_file_path("D:\\", std::string(":"), curr, next); - + EXPECT_EQ(curr,std::string("D:\\")); EXPECT_EQ(next,std::string("")); - + utils::split_file_path("D:\\test\\some\\path", std::string(":"), curr, next); @@ -428,17 +426,17 @@ TEST(conduit_utils, split_windows_paths) utils::split_file_path("D:\\test\\some\\path:subpath", std::string(":"), curr, next); - + EXPECT_EQ(curr,std::string("D:\\test\\some\\path")); EXPECT_EQ(next,std::string("subpath")); utils::split_file_path(std::string(next), std::string(":"), curr, next); - + EXPECT_EQ(curr,std::string("subpath")); EXPECT_EQ(next,std::string("")); - + // drive letter needs '\\', so check corner case where its @@ -446,7 +444,7 @@ TEST(conduit_utils, split_windows_paths) utils::split_file_path("a:subpath", std::string(":"), curr, next); - + EXPECT_EQ(curr,std::string("a")); EXPECT_EQ(next,std::string("subpath")); @@ -491,7 +489,7 @@ TEST(conduit_utils, split_windows_paths) EXPECT_EQ(curr,std::string("subpath")); EXPECT_EQ(next,std::string("a")); - + } //----------------------------------------------------------------------------- @@ -502,10 +500,10 @@ TEST(conduit_utils, split_tests) std::string t("a b c"); std::string p = t; - + std::string curr; std::string next; - + utils::split_string(p," ",curr,next); EXPECT_EQ(curr,std::string("a")); p = next; @@ -525,10 +523,10 @@ TEST(conduit_utils, split_tests) // reverse split // expect c,b,a then empty p = t; - + curr = ""; next = ""; - + utils::rsplit_string(p," ",curr,next); EXPECT_EQ(curr,std::string("c")); p = next; @@ -548,3 +546,156 @@ TEST(conduit_utils, split_tests) } + +//----------------------------------------------------------------------------- +TEST(conduit_utils, format_args) +{ + Node args; + + // args not an obj or list, throw exception + { + args.reset(); + EXPECT_THROW(conduit::utils::format("{a} {b} {c} {d} {e} {f}",args), + conduit::Error); + + args = 32; + EXPECT_THROW(conduit::utils::format("{a} {b} {c} {d} {e} {f}",args), + conduit::Error); + } + + // named args test + args["a"] = "something about"; + args["b"] = "and"; + args["c"] = 3.1415; + args["d"] = 42; + + std::string t = conduit::utils::format("{a} {c:0.3} {b} {d:04}", args); + std::cout << t << std::endl; + + EXPECT_EQ("something about 3.14 and 0042",t); + + // not enough args to exec format, throw exception + EXPECT_THROW(conduit::utils::format("{a} {b} {c} {d} {e} {f}",args), + conduit::Error); + + // ordered args test + args.reset(); + args.append() = "something about"; + args.append() = 3.1415; + args.append() = "and"; + args.append() = 42; + + t = conduit::utils::format("{} {:0.3} {} {:04}", args); + std::cout << t << std::endl; + EXPECT_EQ("something about 3.14 and 0042",t); + + + // mis match args to format req, throw exception + args.reset(); + args.append() = "something about"; + args.append() = "and"; + args.append() = 3.1415; + args.append() = 42; + + EXPECT_THROW(conduit::utils::format("{} {:0.3} {} {:04}", args), + conduit::Error); + + // not enough args to exec format, throw exception + EXPECT_THROW(conduit::utils::format("{} {} {} {} {} {}",args), + conduit::Error); + +} + + +// //----------------------------------------------------------------------------- +TEST(conduit_utils, format_maps) +{ + Node maps; + + // args not an obj or list, throw exception + { + maps.reset(); + EXPECT_THROW(conduit::utils::format("{a} {b} {c} {d} {e} {f}", + maps, + 0), + conduit::Error); + + maps = 32; + EXPECT_THROW(conduit::utils::format("{a} {b} {c} {d} {e} {f}", + maps, + 0), + conduit::Error); + } + + // named args test + maps["a"] = {1.1415, 2.1415, 3.1415}; + maps["b"] = {0,0,42}; + + std::string t = conduit::utils::format("something about {a:0.3} and {b:04}", + maps, + 2); + std::cout << t << std::endl; + EXPECT_EQ("something about 3.14 and 0042",t); + + + // bad map index, (too small) + { + EXPECT_THROW(conduit::utils::format("{a} {b} {c}",maps,-100), + conduit::Error); + + EXPECT_THROW(conduit::utils::format("{a} {b} {c}",maps,-1), + conduit::Error); + } + + // bad map index, (too big) + { + EXPECT_THROW(conduit::utils::format("{a} {b} {c}",maps,4), + conduit::Error); + + EXPECT_THROW(conduit::utils::format("{a} {b} {c}",maps,100), + conduit::Error); + } + + + // not enough args to exec format, throw exception + EXPECT_THROW(conduit::utils::format("{a} {b} {c}",maps,2), + conduit::Error); + + // // ordered args test + maps.reset(); + maps.append() = {1.1415, 2.1415, 3.1415}; + maps.append() = {0,0,42}; + + t = conduit::utils::format("something about {:0.3} and {:04}", + maps, + 2); + std::cout << t << std::endl; + EXPECT_EQ("something about 3.14 and 0042",t); + + // string list case + maps.reset(); + Node &slist = maps.append(); + + slist.append() = "hi 0"; + slist.append() = "hi 1"; + slist.append() = "hi 2"; + + t = conduit::utils::format("{} is it",maps,0); + std::cout << t << std::endl; + EXPECT_EQ("hi 0 is it",t); + + t = conduit::utils::format("{} is it",maps,1); + std::cout << t << std::endl; + EXPECT_EQ("hi 1 is it",t); + + t = conduit::utils::format("{} is it",maps,2); + std::cout << t << std::endl; + EXPECT_EQ("hi 2 is it",t); + + // not enough args to exec format, throw exception + EXPECT_THROW(conduit::utils::format("{} {} {} {} {} {}",maps), + conduit::Error); + +} + +