diff --git a/src/trx.cpp b/src/trx.cpp index 869a734..d53d207 100644 --- a/src/trx.cpp +++ b/src/trx.cpp @@ -849,12 +849,17 @@ std::string get_base(const std::string &delimiter, const std::string &str) { } std::string get_ext(const std::string &str) { + // Extract the extension from the final path component only. Scanning the + // whole path for '.' mistakes a dot in a parent directory (e.g. a + // version-numbered build path like ".../foo-5.4/out") for the extension. + const std::size_t sep = str.find_last_of("/\\"); + const std::string name = (sep == std::string::npos) ? str : str.substr(sep + 1); + std::string ext; constexpr char kDelimiter = '.'; - - const std::size_t pos = str.rfind(kDelimiter); - if (pos != std::string::npos && pos + 1 < str.length()) { - ext = str.substr(pos + 1); + const std::size_t pos = name.rfind(kDelimiter); + if (pos != std::string::npos && pos + 1 < name.length()) { + ext = name.substr(pos + 1); } return ext; } diff --git a/tests/test_trx_io.cpp b/tests/test_trx_io.cpp index 2c5bac0..b03e6ae 100644 --- a/tests/test_trx_io.cpp +++ b/tests/test_trx_io.cpp @@ -839,3 +839,21 @@ TEST(TrxFileIo, export_dpv_to_tsf_errors) { trx::TrxFile empty; EXPECT_THROW(empty.export_dpv_to_tsf("signal", output_path.string(), "1"), trx::TrxFormatError); } + +// get_ext must read the extension from the final path component only. A '.' in +// a parent directory (e.g. a version-numbered build path like ".../foo-5.4/") +// must not be treated as the extension. Regression for "Unsupported extension". +TEST(TrxFileIo, get_ext_uses_final_path_component) { + // Dotted parent directory, file has no extension -> empty (the bug case). + EXPECT_EQ(trx::get_ext("/a/build-5.4/out/trx_test"), ""); + EXPECT_EQ(trx::get_ext("/a/build-5.4/out/trx_test.trx"), "trx"); + // No path separator: whole string is the name. + EXPECT_EQ(trx::get_ext("out.trx"), "trx"); + EXPECT_EQ(trx::get_ext("trx_test"), ""); + // dtype-style array filenames (name.dims.dtype) resolve to the dtype, even + // under a dotted parent directory. + EXPECT_EQ(trx::get_ext("/a/shard.1/positions.3.float32"), "float32"); + EXPECT_EQ(trx::get_ext("offsets.uint64"), "uint64"); + // Trailing dot -> empty. + EXPECT_EQ(trx::get_ext("/a/b.c/name."), ""); +}