diff --git a/.github/workflows/classy-tests.yml b/.github/workflows/classy-tests.yml index 976e90a..b58b878 100644 --- a/.github/workflows/classy-tests.yml +++ b/.github/workflows/classy-tests.yml @@ -53,3 +53,9 @@ jobs: cd Tests/classy_hdf5_interface export LD_LIBRARY_PATH="../../hdf5-1.12.0-install/lib:$LD_LIBRARY_PATH" ./search.exe + + - name: Run ClassyHDF Test - Attributes + run: | + cd Tests/classy_hdf5_interface + export LD_LIBRARY_PATH="../../hdf5-1.12.0-install/lib:$LD_LIBRARY_PATH" + ./attributes.exe diff --git a/Source/ClassyHDF.H b/Source/ClassyHDF.H index 9335945..5da8de4 100644 --- a/Source/ClassyHDF.H +++ b/Source/ClassyHDF.H @@ -10,5 +10,7 @@ #include "ClassyHDF_Location.H" #include "ClassyHDF_Group.H" #include "ClassyHDF_File.H" +#include "ClassyHDF_Errors.H" + +#endif -#endif \ No newline at end of file diff --git a/Source/ClassyHDF_Errors.H b/Source/ClassyHDF_Errors.H new file mode 100644 index 0000000..8293f10 --- /dev/null +++ b/Source/ClassyHDF_Errors.H @@ -0,0 +1,13 @@ +#ifndef CLASSY_HDF_ERRORS_H_ +#define CLASSY_HDF_ERRORS_H_ + +namespace ClassyHDF { + +namespace Errors { + enum {file_exists=1}; +} + +} + +#endif + diff --git a/Source/ClassyHDF_File.H b/Source/ClassyHDF_File.H index b18d2b6..00cbb0a 100644 --- a/Source/ClassyHDF_File.H +++ b/Source/ClassyHDF_File.H @@ -6,11 +6,12 @@ #include "hdf5.h" #include "ClassyHDF_Group.H" +#include "ClassyHDF_Errors.H" namespace ClassyHDF { namespace FileMode { - enum {rw=0, trunc}; + enum {rw=0, trunc, exists_is_error}; } class File : public Location { @@ -22,14 +23,18 @@ class File : public Location { set_name(file_name); // try to open the file in read/write mode, otherwise create it - if (access_type == FileMode::rw) { + if (access_type == FileMode::rw || access_type == FileMode::exists_is_error) { set_existed(true); H5E_BEGIN_TRY set_id(H5Fopen(name().c_str(), H5F_ACC_RDWR, H5P_DEFAULT)); H5E_END_TRY + + if (access_type == FileMode::exists_is_error && id() >= 0) { + throw Errors::file_exists; + } } - if (access_type == FileMode::trunc || + if (access_type == FileMode::trunc || access_type == FileMode::exists_is_error || (access_type == FileMode::rw && id() < 0)) { set_existed(false); set_id(H5Fcreate(name().c_str(), H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT)); diff --git a/Source/ClassyHDF_Location.H b/Source/ClassyHDF_Location.H index d39008d..c0db65d 100644 --- a/Source/ClassyHDF_Location.H +++ b/Source/ClassyHDF_Location.H @@ -4,6 +4,7 @@ #include #include #include "hdf5.h" +#include "hdf5_hl.h" #include "ClassyHDF_Identity.H" #include "ClassyHDF_Data.H" @@ -11,6 +12,117 @@ namespace ClassyHDF { +namespace { + template + struct Attribute { + static void set(const Tloc& loc, const std::string& attr_name, const T& attr_value) { assert(false); } + static T get(const Tloc& loc, const std::string& attr_name) { assert(false); return static_cast(0); } + }; + + template + struct Attribute { + static void set(const Tloc& loc, const std::string& attr_name, const std::string& attr_value) + { + auto gattr = loc.get_group("Attributes"); + + herr_t status = H5LTset_attribute_string(loc.id(), "Attributes", + attr_name.c_str(), + attr_value.c_str()); + assert(status >= 0); + } + + static std::string get(const Tloc& loc, const std::string& attr_name) + { + char* buffer; + int size_buffer; + herr_t status = H5LTget_attribute_ndims(loc.id(), "Attributes", + attr_name.c_str(), &size_buffer); + assert(status >= 0); + + buffer = new char[size_buffer]; + status = H5LTget_attribute_string(loc.id(), "Attributes", + attr_name.c_str(), buffer); + std::string sattr = buffer; + delete buffer; + assert(status >= 0); + + return sattr; + } + }; + + template + struct Attribute { + static void set(const Tloc& loc, const std::string& attr_name, const int& attr_value) + { + auto gattr = loc.get_group("Attributes"); + + herr_t status = H5LTset_attribute_int(loc.id(), "Attributes", + attr_name.c_str(), + &attr_value, 1); + assert(status >= 0); + } + + static int get(const Tloc& loc, const std::string& attr_name) + { + int attr_value = 0; + + herr_t status = H5LTget_attribute_int(loc.id(), "Attributes", + attr_name.c_str(), &attr_value); + assert(status >= 0); + + return attr_value; + } + }; + + template + struct Attribute { + static void set(const Tloc& loc, const std::string& attr_name, const float& attr_value) + { + auto gattr = loc.get_group("Attributes"); + + herr_t status = H5LTset_attribute_float(loc.id(), "Attributes", + attr_name.c_str(), + &attr_value, 1); + assert(status >= 0); + } + + static float get(const Tloc& loc, const std::string& attr_name) + { + float attr_value = 0; + + herr_t status = H5LTget_attribute_float(loc.id(), "Attributes", + attr_name.c_str(), &attr_value); + assert(status >= 0); + + return attr_value; + } + }; + + template + struct Attribute { + static void set(const Tloc& loc, const std::string& attr_name, const double& attr_value) + { + auto gattr = loc.get_group("Attributes"); + + herr_t status = H5LTset_attribute_double(loc.id(), "Attributes", + attr_name.c_str(), + &attr_value, 1); + assert(status >= 0); + } + + static double get(const Tloc& loc, const std::string& attr_name) + { + double attr_value = 0; + + herr_t status = H5LTget_attribute_double(loc.id(), "Attributes", + attr_name.c_str(), &attr_value); + assert(status >= 0); + + return attr_value; + } + }; +} + // We're going to use the Curiously Recurring Template Pattern to // allow any derived class from Location to create Group objects, // even though Group derives from Location. @@ -178,6 +290,16 @@ class Location : public NamedIdentity { dataset.append(data); return dataset; } + + template + void attr(const std::string& attr_name, const T& attr_value) { + Attribute::set(*this, attr_name, attr_value); + } + + template + T attr(const std::string& attr_name) { + return Attribute::get(*this, attr_name); + } }; } diff --git a/Source/Make.package b/Source/Make.package index 47f23f0..72bcde6 100644 --- a/Source/Make.package +++ b/Source/Make.package @@ -7,4 +7,7 @@ CEXE_headers += ClassyHDF_Dataset.H CEXE_headers += ClassyHDF_Location.H CEXE_headers += ClassyHDF_Group.H CEXE_headers += ClassyHDF_File.H -CEXE_headers += ClassyHDF.H \ No newline at end of file +CEXE_headers += ClassyHDF_Errors.H +CEXE_headers += ClassyHDF.H + +LIBRARIES += -lhdf5_hl diff --git a/Tests/classy_hdf5_interface/Makefile b/Tests/classy_hdf5_interface/Makefile index e490238..0ea4a9a 100644 --- a/Tests/classy_hdf5_interface/Makefile +++ b/Tests/classy_hdf5_interface/Makefile @@ -3,12 +3,14 @@ EXTRA_LIBS ?= -lsz -lz -lm all: g++ -o append.exe append.cpp -I../../Source -I$(HDF5_HOME)/include -L$(HDF5_HOME)/lib -lhdf5 $(EXTRA_LIBS) + g++ -o attributes.exe attributes.cpp -I../../Source -I$(HDF5_HOME)/include -L$(HDF5_HOME)/lib -lhdf5 -lhdf5_hl $(EXTRA_LIBS) g++ -o get_last_N.exe get_last_N.cpp -I../../Source -I$(HDF5_HOME)/include -L$(HDF5_HOME)/lib -lhdf5 $(EXTRA_LIBS) g++ -o resize.exe resize.cpp -I../../Source -I$(HDF5_HOME)/include -L$(HDF5_HOME)/lib -lhdf5 $(EXTRA_LIBS) g++ -o search.exe search.cpp -I../../Source -I$(HDF5_HOME)/include -L$(HDF5_HOME)/lib -lhdf5 $(EXTRA_LIBS) test: all ./append.exe + ./attributes.exe ./get_last_N.exe ./resize.exe ./search.exe diff --git a/Tests/classy_hdf5_interface/attributes.cpp b/Tests/classy_hdf5_interface/attributes.cpp new file mode 100644 index 0000000..e284bce --- /dev/null +++ b/Tests/classy_hdf5_interface/attributes.cpp @@ -0,0 +1,57 @@ +#include +#include +#include +#include "ClassyHDF.H" +#include "hdf5_hl.h" + +using namespace ClassyHDF; + +void write_test_file(const std::string& filename) { + File file(filename, FileMode::trunc); + Group group1 = file.get_group("Data"); + Group group2 = group1.get_group("GroupA"); + + // put some attributes into the "GroupA" group + group2.attr("TestAttribute", "Hello World!"); + group2.attr("TestAttInt", 5); + group2.attr("TestAttFloat", -1.0); + group2.attr("TestAttDouble", -2.0); +} + +bool do_test(const std::string& filename) { + File file(filename); + Group group1 = file.get_group("Data"); + Group group2 = group1.get_group("GroupA"); + + bool success = true; + + // get some attributes from the "GroupA" group + std::string stest = group2.attr("TestAttribute"); + success = success && (stest == "Hello World!"); + + int itest = group2.attr("TestAttInt"); + success = success && (itest == 5); + + float ftest = group2.attr("TestAttFloat"); + success = success && (ftest == -1.0); + + double dtest = group2.attr("TestAttDouble"); + success = success && (dtest == -2.0); + + return success; +} + +int main() { + const std::string filename = "file_attributes.h5"; + + write_test_file(filename); + + if (do_test(filename)) { + std::cout << "success" << std::endl; + return 0; + } else { + std::cout << "failure" << std::endl; + return -1; + } +} +