diff --git a/.gitignore b/.gitignore index 2108110..15b420c 100644 --- a/.gitignore +++ b/.gitignore @@ -86,6 +86,7 @@ test/reports/*.xml test/*.trs test/region.reg test/test_python.sh +test/test_csv*.dat examples/python/*.xml examples/python/*.html diff --git a/ChangeLog b/ChangeLog index 507affe..2ab9271 100644 --- a/ChangeLog +++ b/ChangeLog @@ -3,6 +3,7 @@ * Version 1.1.0 released ========================= + Add GCsv::append() method (#1773). Add GTestSuite::test_value() string method (#1772). Add component setting methods to GModelSky (#1770). Add GApplication::logFileClose() method. diff --git a/NEWS b/NEWS index 0d5af85..68e7646 100644 --- a/NEWS +++ b/NEWS @@ -24,6 +24,7 @@ The following methods have been added to the public interface: - GFilaname::protocol() - GFilename::path() - GFilename::file() +- GCsv::append() - GModelSky::spatial() - GModelSky::spectral() - GModelSky::temporal() @@ -228,6 +229,9 @@ safe (#1717). The gammalib::number() function was added to convert a singular noun into a plural noun is the number of instances of the noun is not one (#1743). +The GCsv::append() method appends a list of strings to a CSV file. This allow +easy creation of a CSV file, in particular from Python lists (#1773). + 14. XML module -------------- diff --git a/include/GCsv.hpp b/include/GCsv.hpp index f45e6b7..4cb6d81 100644 --- a/include/GCsv.hpp +++ b/include/GCsv.hpp @@ -78,6 +78,7 @@ public: const int& nrows(void) const; const int& precision(void) const; void precision(const int& precision); + void append(const std::vector& list); std::string string(const int& row, const int& col) const; double real(const int& row, const int& col) const; int integer(const int& row, const int& col) const; diff --git a/pyext/GCsv.i b/pyext/GCsv.i index 04cbfae..9f2b7bd 100644 --- a/pyext/GCsv.i +++ b/pyext/GCsv.i @@ -28,7 +28,10 @@ #include "GCsv.hpp" #include "GTools.hpp" %} -%include stl.i +%include "stl.i" +%include "std_vector.i" +%template(vectors) std::vector; + /***********************************************************************//** * @brief Tuple to index conversion to provide element access. @@ -100,6 +103,7 @@ public: const int& nrows(void) const; const int& precision(void) const; void precision(const int& precision); + void append(const std::vector& list); std::string string(const int& row, const int& col) const; double real(const int& row, const int& col) const; int integer(const int& row, const int& col) const; diff --git a/src/support/GCsv.cpp b/src/support/GCsv.cpp index 71e74be..39d42b5 100644 --- a/src/support/GCsv.cpp +++ b/src/support/GCsv.cpp @@ -36,6 +36,7 @@ /* __ Method name definitions ____________________________________________ */ #define G_ACCESS "GCsv::operator()(int&, int&)" +#define G_APPEND "GCsv::append(std::vector&)" #define G_LOAD "GCsv::load(GFilename&, std::string&)" #define G_SAVE "GCsv::save(GFilename&, std::string&, bool&)" @@ -250,6 +251,48 @@ GCsv* GCsv::clone(void) const /***********************************************************************//** + * @brief Append list of strings + * + * @param[in] list List of strings. + * + * @exception GException::invalid_argument + * Invalid number of elements in list. + * + * Appends a list of strings to the CSV table. If the table is empty, the + * number of elements in the list will determine the number of columns of + * the table. If a table exists already, the method will throw an exception + * if the number of elements in the list does not correspond to the number + * of table columns. + ***************************************************************************/ +void GCsv::append(const std::vector& list) +{ + // If we have already rows then check columns size ... + if (m_rows > 0) { + if (m_cols != list.size()) { + std::string msg = "Invalid attempt to append "+ + gammalib::str(list.size())+" elements to a CSV " + "table with "+gammalib::str(m_cols)+" columns."; + throw GException::invalid_argument(G_APPEND, msg); + } + } + + // ... otherwise store the number of columns + else { + m_cols = list.size(); + } + + // Append + m_data.push_back(list); + + // Increment number of rows + m_rows++; + + // Return + return; +} + + +/***********************************************************************//** * @brief Get string value * * @param[in] row Table row. @@ -482,7 +525,9 @@ void GCsv::save(const GFilename& filename, // Write columns for (int col = 0; col < m_cols; ++col) { std::fputs(m_data[row][col].c_str(), fptr); - std::fputs(sep.c_str(), fptr); + if (col < m_cols-1) { + std::fputs(sep.c_str(), fptr); + } } // Write linebreak @@ -525,7 +570,7 @@ std::string GCsv::print(const GChatter& chatter) const result.append("default"); } else { - result.append(gammalib::str(m_precision)); + result.append(gammalib::str(m_precision)); } } // endif: chatter was not silent diff --git a/test/data/csv.dat b/test/data/csv.dat new file mode 100644 index 0000000..01cf1fe --- /dev/null +++ b/test/data/csv.dat @@ -0,0 +1,5 @@ +ra,dec,duration +10.0,-12.0,1800.0 +12.0,-10.0,1000.0 +14.0,-8.0,100.0 + diff --git a/test/test_GSupport.cpp b/test/test_GSupport.cpp index 99cd7a1..138e3c2 100644 --- a/test/test_GSupport.cpp +++ b/test/test_GSupport.cpp @@ -53,9 +53,10 @@ void TestGSupport::set(void){ append(static_cast(&TestGSupport::test_expand_env), "Test Environment variable"); append(static_cast(&TestGSupport::test_node_array), "Test GNodeArray"); append(static_cast(&TestGSupport::test_bilinear), "Test GBilinear"); - append(static_cast(&TestGSupport::test_url_file), "Test GUrlFile"); + append(static_cast(&TestGSupport::test_url_file), "Test GUrlFile"); append(static_cast(&TestGSupport::test_url_string), "Test GUrlString"); append(static_cast(&TestGSupport::test_filename), "Test GFilename"); + append(static_cast(&TestGSupport::test_csv), "Test GCsv"); // Return return; @@ -858,6 +859,106 @@ void TestGSupport::test_filename(void) /***********************************************************************//** + * @brief Test GCsv class + * + * Test the GCsv class. + ***************************************************************************/ +void TestGSupport::test_csv(void) +{ + // Test void constructor + GCsv csv1; + test_value(csv1.size(), 0); + test_value(csv1.ncols(), 0); + test_value(csv1.nrows(), 0); + + // Test rows and columns constructor + GCsv csv2(3,4); + test_value(csv2.size(), 12); + test_value(csv2.ncols(), 4); + test_value(csv2.nrows(), 3); + + // Test filename constructor + GCsv csv3("data/csv.dat", ","); + test_value(csv3.size(), 12); + test_value(csv3.ncols(), 3); + test_value(csv3.nrows(), 4); + + // Test copy constructor + GCsv csv4(csv3); + test_value(csv3.size(), 12); + test_value(csv3.ncols(), 3); + test_value(csv3.nrows(), 4); + + // Test clear method + csv4.clear(); + test_value(csv4.size(), 0); + test_value(csv4.ncols(), 0); + test_value(csv4.nrows(), 0); + + // Test clone method + GCsv* csv5 = csv3.clone(); + test_value(csv5->size(), 12); + test_value(csv5->ncols(), 3); + test_value(csv5->nrows(), 4); + + // Test append method + std::vector row1; + std::vector row2; + row1.push_back("ra"); + row1.push_back("dec"); + row1.push_back("durations"); + row2.push_back("10.0"); + row2.push_back("-10.0"); + row2.push_back("1000.0"); + csv4.append(row1); + csv4.append(row2); + test_value(csv4.size(), 6); + test_value(csv4.ncols(), 3); + test_value(csv4.nrows(), 2); + + // Test operator access method + test_value(csv4(0,0), "ra"); + test_value(csv4(0,1), "dec"); + test_value(csv4(0,2), "durations"); + test_value(csv4(1,0), "10.0"); + test_value(csv4(1,1), "-10.0"); + test_value(csv4(1,2), "1000.0"); + + // Test access methods + test_value(csv4.string(1,0), "10.0"); + test_value(csv4.real(1,0), 10.0); + test_value(csv4.integer(1,0), 10); + + // Test set methods + csv4.string(1,0,"11.0"); + test_value(csv4.string(1,0), "11.0"); + csv4.real(1,0, 12.0); + test_value(csv4.real(1,0), 12.0); + csv4.integer(1,0, 13); + test_value(csv4.integer(1,0), 13); + + // Test save method (make sure that former result is overwritten) + csv4.save("test_csv.dat", ",", true); + + // Test load method + GCsv csv6; + csv6.load("test_csv.dat", ","); + test_value(csv6.size(), 6); + test_value(csv6.ncols(), 3); + test_value(csv6.nrows(), 2); + test_value(csv6(0,0), "ra"); + test_value(csv6(0,1), "dec"); + test_value(csv6(0,2), "durations"); + test_value(csv6(1,0), "13"); + test_value(csv6(1,1), "-10.0"); + test_value(csv6(1,2), "1000.0"); + + // Return + return; +} + + +/***********************************************************************//** * @brief Main test entry point ***************************************************************************/ int main(void) diff --git a/test/test_GSupport.hpp b/test/test_GSupport.hpp index f78967c..c3f58b7 100644 --- a/test/test_GSupport.hpp +++ b/test/test_GSupport.hpp @@ -1,7 +1,7 @@ /*************************************************************************** * test_GSupport.hpp - test support module * * ----------------------------------------------------------------------- * - * copyright (C) 2012-2015 by Jean-Baptiste Cayrou * + * copyright (C) 2012-2016 by Jean-Baptiste Cayrou * * ----------------------------------------------------------------------- * * * * This program is free software: you can redistribute it and/or modify * @@ -54,6 +54,7 @@ public: void test_url_file(void); void test_url_string(void); void test_filename(void); + void test_csv(void); private: // Private methods diff --git a/test/test_GSupport.py b/test/test_GSupport.py index ff3d046..a64cd0e 100755 --- a/test/test_GSupport.py +++ b/test/test_GSupport.py @@ -1,7 +1,7 @@ # ========================================================================== # This module performs unit tests for the GammaLib support module. # -# Copyright (C) 2012-2015 Juergen Knoedlseder +# Copyright (C) 2012-2016 Juergen Knoedlseder # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -45,13 +45,14 @@ class Test(gammalib.GPythonTestSuite): Set all test functions. """ # Set test name - self.name("support") + self.name('support') # Append tests - self.append(self.test_node_array, "Test GNodeArray") - self.append(self.test_url_file, "Test GUrlFile") - self.append(self.test_url_string, "Test GUrlString") - self.append(self.test_filename, "Test GFilename") + self.append(self.test_node_array, 'Test GNodeArray') + self.append(self.test_url_file, 'Test GUrlFile') + self.append(self.test_url_string, 'Test GUrlString') + self.append(self.test_filename, 'Test GFilename') + self.append(self.test_csv, 'Test GCsv') # Return return @@ -96,18 +97,16 @@ class Test(gammalib.GPythonTestSuite): Test GUrlFile class. """ # Test file writing - url = gammalib.GUrlFile("test_url.dat", "w") - self.test_value(url.write("abcd", 4), 4) - url.put_char(ord("e")) + url = gammalib.GUrlFile('test_url.dat', 'w') + self.test_value(url.write('abcd', 4), 4) + url.put_char(ord('e')) url.close() # Test file reading - buffer = "" - url = gammalib.GUrlFile("test_url.dat", "r") + buffer = '' + url = gammalib.GUrlFile('test_url.dat', 'r') buffer = url.read(99) - self.test_assert(buffer == "abcde", - "Expected \"abcde\" in file, found \""+ - buffer+"\"") + self.test_value(buffer, 'abcde') url.close() # Return @@ -120,16 +119,14 @@ class Test(gammalib.GPythonTestSuite): """ # Test writing url = gammalib.GUrlString() - self.test_value(url.write("abcd", 4), 4) - url.put_char(ord("e")) + self.test_value(url.write('abcd', 4), 4) + url.put_char(ord('e')) # Test reading url.rewind() - buffer = "" + buffer = '' buffer = url.read(99) - self.test_assert(buffer == "abcde", - "Expected \"abcde\" in file, found \""+ - buffer+"\"") + self.test_value(buffer, 'abcde') url.close() # Return @@ -141,29 +138,60 @@ class Test(gammalib.GPythonTestSuite): Test GFilename class. """ # Test assigning file names - filename = gammalib.GFilename("myfile.fits") - self.test_assert(filename.url() == "myfile.fits", - "Expected \"myfile.fits\", found \""+ - filename.url()+"\"") - filename = gammalib.GFilename("myfile.fits[EVENTS]") - self.test_assert(filename.url() == "myfile.fits", - "Expected \"myfile.fits\", found \""+ - filename.url()+"\"") - self.test_assert(filename.extname() == "EVENTS", - "Expected extension \"EVENTS\", found \""+ - filename.extname()+"\"") - filename = gammalib.GFilename("myfile.fits[0]") - self.test_assert(filename.url() == "myfile.fits", - "Expected \"myfile.fits\", found \""+ - filename.url()+"\"") + filename = gammalib.GFilename('myfile.fits') + self.test_value(filename.url(), 'myfile.fits') + # + filename = gammalib.GFilename('myfile.fits[EVENTS]') + self.test_value(filename.url(), 'myfile.fits') + self.test_value(filename.extname(), 'EVENTS') + # + filename = gammalib.GFilename('myfile.fits[0]') + self.test_value(filename.url(), 'myfile.fits') self.test_value(filename.extno(), 0) - filename = gammalib.GFilename("myfile.fits[0,2]") - self.test_assert(filename.url() == "myfile.fits", - "Expected \"myfile.fits\", found \""+ - filename.url()+"\"") + # + filename = gammalib.GFilename('myfile.fits[0,2]') + self.test_value(filename.url(), 'myfile.fits') self.test_value(filename.extno(), 0) self.test_value(filename.extver(), 2) # Return return - \ No newline at end of file + + # Test GCsv class + def test_csv(self): + """ + Test GCsv class. + """ + # Create CSV file using the append method + csv = gammalib.GCsv() + csv.append(['ra','dec','duration']) + csv.append(['20.0','-70.0','1800.0']) + + # Test created file + self.test_value(csv.size(), 6) + self.test_value(csv.ncols(), 3) + self.test_value(csv.nrows(), 2) + self.test_value(csv[0,0], 'ra') + self.test_value(csv[0,1], 'dec') + self.test_value(csv[0,2], 'duration') + self.test_value(csv[1,0], '20.0') + self.test_value(csv[1,1], '-70.0') + self.test_value(csv[1,2], '1800.0') + + # Test saving and loading of file + csv.save('test_csv_py.dat', ',', True) + csv.load('test_csv_py.dat', ',') + + # Test loaded file + self.test_value(csv.size(), 6) + self.test_value(csv.ncols(), 3) + self.test_value(csv.nrows(), 2) + self.test_value(csv[0,0], 'ra') + self.test_value(csv[0,1], 'dec') + self.test_value(csv[0,2], 'duration') + self.test_value(csv[1,0], '20.0') + self.test_value(csv[1,1], '-70.0') + self.test_value(csv[1,2], '1800.0') + + # Return + return