diff --git a/README.md b/README.md index 49c3be38..28a16a36 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -Library :: Core -=============== +Library ▸ Core +============== Fundamental types, containers and utilities. diff --git a/docs/doxygen/html/header.html b/docs/doxygen/html/header.html index f7f7bba5..0233f411 100755 --- a/docs/doxygen/html/header.html +++ b/docs/doxygen/html/header.html @@ -18,7 +18,7 @@
-
+
$searchbox diff --git a/include/Library/Core/Containers/Table.hpp b/include/Library/Core/Containers/Table.hpp index d3397781..e3f29347 100755 --- a/include/Library/Core/Containers/Table.hpp +++ b/include/Library/Core/Containers/Table.hpp @@ -113,7 +113,7 @@ class Table /// @endcode /// /// @param [in] aTable A table - /// @return True if tables not are equal + /// @return True if tables are not equal bool operator != ( const Table& aTable ) const ; @@ -137,11 +137,26 @@ class Table /// @endcode /// /// @param [in] aRowIndex A row index + /// @param [in] aColumnIndex A column index /// @return Reference to cell const Cell& operator () ( const Index& aRowIndex, const Index& aColumnIndex ) const ; + /// @brief Function call operator (cell accessor) + /// + /// @code + /// Table table = { { "Column A", "Column B" }, { { Cell::Integer(123), Cell::Real(123.456) } } } ; + /// const Cell& cell = table(0, "Column A") ; // Cell::Integer(123) + /// @endcode + /// + /// @param [in] aRowIndex A row index + /// @param [in] aColumnName A column name + /// @return Reference to cell + + const Cell& operator () ( const Index& aRowIndex, + const String& aColumnName ) const ; + /// @brief Output stream operator /// /// @code @@ -165,6 +180,13 @@ class Table bool isEmpty ( ) const ; + /// @brief Returns true is table has column with a given name + /// + /// @param [in] aColumnName A column name + /// @return True is table has column with a given name + + bool hasColumnWithName ( const String& aColumnName ) const ; + /// @brief Get number of rows /// /// @code @@ -187,6 +209,13 @@ class Table Size getColumnCount ( ) const ; + /// @brief Returns the index of column with a given name + /// + /// @param [in] aColumnName A column name + /// @return Index of column with a given name + + Index getIndexOfColumnWithName ( const String& aColumnName ) const ; + /// @brief Add row /// /// @code diff --git a/include/Library/Core/Containers/Table/Row.hpp b/include/Library/Core/Containers/Table/Row.hpp index 78d25666..74fb0b29 100755 --- a/include/Library/Core/Containers/Table/Row.hpp +++ b/include/Library/Core/Containers/Table/Row.hpp @@ -37,6 +37,7 @@ namespace table using library::core::types::Index ; using library::core::types::Size ; +using library::core::types::String ; using library::core::ctnr::Array ; using library::core::ctnr::table::Cell ; @@ -70,6 +71,8 @@ class Row const Cell& operator [] ( const Index& aColumnIndex ) const ; + const Cell& operator [] ( const String& aColumnName ) const ; + bool isEmpty ( ) const ; Size getSize ( ) const ; diff --git a/include/Library/Core/Logger.hpp b/include/Library/Core/Logger.hpp index b9aedd7e..ec6010e1 100755 --- a/include/Library/Core/Logger.hpp +++ b/include/Library/Core/Logger.hpp @@ -110,7 +110,7 @@ class Logger #define LOG_SCOPE0() BOOST_LOG_NAMED_SCOPE("?") #define LOG_SCOPE1(aScope) BOOST_LOG_NAMED_SCOPE(aScope) -#define LOG_SCOPE2(aClass, aMethod) BOOST_LOG_NAMED_SCOPE(aClass " :: " aMethod) +#define LOG_SCOPE2(aClass, aMethod) BOOST_LOG_NAMED_SCOPE(aClass " ▸ " aMethod) #define LOG_SCOPE(...) GET_MACRO(_0, ##__VA_ARGS__, LOG_SCOPE2, LOG_SCOPE1, LOG_SCOPE0)(__VA_ARGS__) diff --git a/share/python/README.md b/share/python/README.md index 13c0cbf8..eb1bdb12 100644 --- a/share/python/README.md +++ b/share/python/README.md @@ -1,2 +1,2 @@ -Library :: Core :: Python Bindings -================================== \ No newline at end of file +Library ▸ Core ▸ Python Bindings +================================ \ No newline at end of file diff --git a/share/python/notebooks/Tutorial.ipynb b/share/python/notebooks/Reference.ipynb similarity index 99% rename from share/python/notebooks/Tutorial.ipynb rename to share/python/notebooks/Reference.ipynb index 41ce5271..f1789061 100644 --- a/share/python/notebooks/Tutorial.ipynb +++ b/share/python/notebooks/Reference.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Library :: Core" + "# Library ▸ Core" ] }, { diff --git a/share/util/Test.cxx b/share/util/Test.cxx index c406a6e3..d9936bfb 100644 --- a/share/util/Test.cxx +++ b/share/util/Test.cxx @@ -41,7 +41,7 @@ std::ostream& operator << ( const TestClass& aTestClass ) { - anOutputStream << "Class :: " << aTestClass.integer_ << " " << aTestClass.double_ ; + anOutputStream << "Class ▸ " << aTestClass.integer_ << " " << aTestClass.double_ ; return anOutputStream ; diff --git a/src/Library/Core/Containers/Table.cpp b/src/Library/Core/Containers/Table.cpp index f84c0610..5dd2d3f1 100644 --- a/src/Library/Core/Containers/Table.cpp +++ b/src/Library/Core/Containers/Table.cpp @@ -119,6 +119,19 @@ const Cell& Table::operator () ( } +const Cell& Table::operator () ( const Index& aRowIndex, + const String& aColumnName ) const +{ + + if (aRowIndex >= rows_.getSize()) + { + throw library::core::error::RuntimeError("Row index [{}] out of range [0 - {}].", aRowIndex, rows_.getSize()) ; + } + + return rows_[aRowIndex][this->getIndexOfColumnWithName(aColumnName)] ; + +} + std::ostream& operator << ( std::ostream& anOutputStream, const Table& aTable ) { @@ -200,6 +213,18 @@ bool Table::isEmpty ( ) return rows_.isEmpty() ; } +bool Table::hasColumnWithName ( const String& aColumnName ) const +{ + + if (aColumnName.isEmpty()) + { + throw library::core::error::runtime::Undefined("Column name") ; + } + + return header_.contains(aColumnName) ; + +} + Size Table::getRowCount ( ) const { return rows_.getSize() ; @@ -210,6 +235,23 @@ Size Table::getColumnCount ( ) return header_.getSize() ; } +Index Table::getIndexOfColumnWithName ( const String& aColumnName ) const +{ + + if (aColumnName.isEmpty()) + { + throw library::core::error::runtime::Undefined("Column name") ; + } + + if (!header_.contains(aColumnName)) // Double query... should be optimized + { + throw library::core::error::RuntimeError("Table does not have any column with name [{}].", aColumnName) ; + } + + return header_.getIndexOf(aColumnName) ; + +} + void Table::addRow ( const Row& aRow ) { @@ -310,7 +352,16 @@ Table Table::LoadCsv ( for (const auto& columnName : document.GetColumnNames()) { - header.add(columnName) ; + + if ((columnName.front() == '"') && (columnName.back() == '"')) + { + header.add(columnName.substr(1, columnName.length() - 2)) ; + } + else + { + header.add(columnName) ; + } + } } diff --git a/src/Library/Core/Containers/Table/Row.cpp b/src/Library/Core/Containers/Table/Row.cpp index 8c8386a6..a46302a2 100644 --- a/src/Library/Core/Containers/Table/Row.cpp +++ b/src/Library/Core/Containers/Table/Row.cpp @@ -8,6 +8,7 @@ //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #include +#include #include #include @@ -108,6 +109,18 @@ const Cell& Row::operator [] ( } +const Cell& Row::operator [] ( const String& aColumnName ) const +{ + + if (tablePtr_ == nullptr) + { + throw library::core::error::RuntimeError("No associated table.") ; + } + + return (*this)[tablePtr_->getIndexOfColumnWithName(aColumnName)] ; + +} + bool Row::isEmpty ( ) const { return cells_.isEmpty() ; diff --git a/src/Library/Core/Types/Real.cpp b/src/Library/Core/Types/Real.cpp index d49ef1de..3d15e2b5 100644 --- a/src/Library/Core/Types/Real.cpp +++ b/src/Library/Core/Types/Real.cpp @@ -755,7 +755,16 @@ types::String Real::toString ( if (this->isInteger()) { - return boost::lexical_cast(value_) + ".0" ; + + types::String realString = boost::lexical_cast(value_) ; + + if (realString.find('e') == std::string::npos) + { + realString += ".0" ; + } + + return realString ; + } // types::String realString = std::to_string(value_) ; @@ -769,7 +778,10 @@ types::String Real::toString ( // types::String realString = stringStream.str() ; - realString.erase(realString.find_last_not_of('0') + 1, std::string::npos) ; // Remove trailing zeros if any + if (realString.find('e') == std::string::npos) + { + realString.erase(realString.find_last_not_of('0') + 1, std::string::npos) ; // Remove trailing zeros if any + } return realString ; diff --git a/test/Library/Core/Containers/Table.test.cpp b/test/Library/Core/Containers/Table.test.cpp index 13af8d01..0ac9d715 100755 --- a/test/Library/Core/Containers/Table.test.cpp +++ b/test/Library/Core/Containers/Table.test.cpp @@ -339,6 +339,10 @@ TEST (Library_Core_Containers_Table, SubscriptOperator) EXPECT_EQ(Object::Real(123.456), firstRow[1]) ; EXPECT_ANY_THROW(firstRow[2]) ; + EXPECT_EQ(Object::Integer(123), firstRow["Column A"]) ; + EXPECT_EQ(Object::Real(123.456), firstRow["Column B"]) ; + EXPECT_ANY_THROW(firstRow["Column C"]) ; + EXPECT_NO_THROW(table[1]) ; const Row& secondRow = table[1] ; @@ -347,11 +351,18 @@ TEST (Library_Core_Containers_Table, SubscriptOperator) EXPECT_FALSE(secondRow[1].isDefined()) ; EXPECT_ANY_THROW(secondRow[2]) ; + EXPECT_EQ(Object::String("Hello"), secondRow["Column A"]) ; + EXPECT_FALSE(secondRow["Column B"].isDefined()) ; + EXPECT_ANY_THROW(secondRow["Column C"]) ; + const Row& thirdRow = table[2] ; EXPECT_EQ(Object::String("World!"), thirdRow[0]) ; EXPECT_ANY_THROW(thirdRow[1]) ; + EXPECT_EQ(Object::String("World!"), thirdRow["Column A"]) ; + EXPECT_ANY_THROW(thirdRow["Column B"]) ; + EXPECT_ANY_THROW(table[3]) ; } @@ -394,6 +405,33 @@ TEST (Library_Core_Containers_Table, FunctionCallOperator) } + { + + const Array header = { "Column A", "Column B" } ; + const Array rows = + { + { Object::Integer(123), Object::Real(123.456) }, + { Object::String("Hello"), Object::Undefined() }, + { Object::String("World!") } + } ; + + const Table table = { header, rows } ; + + EXPECT_EQ(Object::Integer(123), table(0, "Column A")) ; + EXPECT_EQ(Object::Real(123.456), table(0, "Column B")) ; + EXPECT_ANY_THROW(table(0, "Column C")) ; + + EXPECT_EQ(Object::String("Hello"), table(1, "Column A")) ; + EXPECT_FALSE(table(1, "Column B").isDefined()) ; + EXPECT_ANY_THROW(table(1, "Column C")) ; + + EXPECT_EQ(Object::String("World!"), table(2, "Column A")) ; + EXPECT_ANY_THROW(table(2, "Column B")) ; + + EXPECT_ANY_THROW(table(3, "Column A")) ; + + } + } TEST (Library_Core_Containers_Table, StreamOperator) @@ -532,6 +570,61 @@ TEST (Library_Core_Containers_Table, IsEmpty) } +TEST (Library_Core_Containers_Table, HasColumnWithName) +{ + + using library::core::types::String ; + using library::core::ctnr::Array ; + using library::core::ctnr::Object ; + using library::core::ctnr::Table ; + using library::core::ctnr::table::Row ; + + { + + const Array header = { "Column A", "Column B" } ; + const Array rows = + { + { Object::Integer(123), Object::Real(123.456) }, + { Object::String("Hello"), Object::Undefined() }, + { Object::String("World!") } + } ; + + const Table table = { header, rows } ; + + EXPECT_TRUE(table.hasColumnWithName("Column A")) ; + EXPECT_TRUE(table.hasColumnWithName("Column B")) ; + + EXPECT_FALSE(table.hasColumnWithName("Column C")) ; + + } + + { + + const Array header = { "Column A", "Column B" } ; + + const Table table = { header } ; + + EXPECT_TRUE(table.hasColumnWithName("Column A")) ; + EXPECT_TRUE(table.hasColumnWithName("Column B")) ; + + EXPECT_FALSE(table.hasColumnWithName("Column C")) ; + + } + + { + + EXPECT_FALSE(Table::Empty().hasColumnWithName("Column A")) ; + + } + + { + + EXPECT_ANY_THROW(Table::Empty().hasColumnWithName("")) ; + + } + +} + TEST (Library_Core_Containers_Table, GetRowCount) { @@ -598,6 +691,43 @@ TEST (Library_Core_Containers_Table, GetColumnCount) } +TEST (Library_Core_Containers_Table, GetIndexOfColumnWithName) +{ + + using library::core::types::String ; + using library::core::ctnr::Array ; + using library::core::ctnr::Object ; + using library::core::ctnr::Table ; + using library::core::ctnr::table::Row ; + + { + + const Array header = { "Column A", "Column B" } ; + const Array rows = + { + { Object::Integer(123), Object::Real(123.456) }, + { Object::String("Hello"), Object::Undefined() }, + { Object::String("World!") } + } ; + + const Table table = { header, rows } ; + + EXPECT_EQ(0, table.getIndexOfColumnWithName("Column A")) ; + EXPECT_EQ(1, table.getIndexOfColumnWithName("Column B")) ; + + EXPECT_ANY_THROW(table.getIndexOfColumnWithName("Column C")) ; + + } + + { + + EXPECT_ANY_THROW(Table::Empty().getIndexOfColumnWithName("")) ; + EXPECT_ANY_THROW(Table::Empty().getIndexOfColumnWithName("Column A")) ; + + } + +} + TEST (Library_Core_Containers_Table, AddRow) { @@ -718,6 +848,25 @@ TEST (Library_Core_Containers_Table, Load) } + { + + + const Array header = { "Start Time (UTCG)", "Stop Time (UTCG)", "Duration (sec)", "Obstruction", "Current Condition", "Worst Condition", "Total Duration (sec)" } ; + const Array rows = + { + { Object::String("1 Jan 2018 00:00:00.000"), Object::String("1 Jan 2018 04:15:48.956"), Object::Real(15348.956), Object::String("Earth"), Object::String("Umbra"), Object::String("Umbra"), Object::Real(15492.990) } + } ; + + const Table referenceTable = { header, rows } ; + + const File file = File::Path(Path::Parse("../test/Library/Core/Containers/Table/B.csv")) ; + + const Table table = Table::Load(file) ; + + EXPECT_EQ(referenceTable, table) ; + + } + } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// \ No newline at end of file diff --git a/test/Library/Core/Containers/Table/B.csv b/test/Library/Core/Containers/Table/B.csv new file mode 100644 index 00000000..033a6f59 --- /dev/null +++ b/test/Library/Core/Containers/Table/B.csv @@ -0,0 +1,2 @@ +"Start Time (UTCG)","Stop Time (UTCG)","Duration (sec)","Obstruction","Current Condition","Worst Condition","Total Duration (sec)" +1 Jan 2018 00:00:00.000,1 Jan 2018 04:15:48.956,15348.956,"Earth","Umbra","Umbra",15492.990 \ No newline at end of file diff --git a/test/Library/Core/Types/Real.test.cpp b/test/Library/Core/Types/Real.test.cpp index 76c3476f..14a71d11 100644 --- a/test/Library/Core/Types/Real.test.cpp +++ b/test/Library/Core/Types/Real.test.cpp @@ -2081,7 +2081,7 @@ TEST (Library_Core_Types_Real, ToString) EXPECT_EQ("112345678912345.59", Real(112345678912345.6).toString()) ; EXPECT_EQ("1123456789123456.8", Real(1123456789123456.7).toString()) ; EXPECT_EQ("11234567891234568.0", Real(11234567891234567.8).toString()) ; - EXPECT_EQ("1.1234567891234568e+17.0", Real(112345678912345678.9).toString()) ; + EXPECT_EQ("1.1234567891234568e+17", Real(112345678912345678.9).toString()) ; } @@ -2217,6 +2217,30 @@ TEST (Library_Core_Types_Real, ToString) } + { + + EXPECT_EQ("1.0000000000000001e+300", Real(1e+300).toString()) << Real(1e+300).toString() ; + EXPECT_EQ("1.0000000000000001e-30", Real(1e-30).toString()) << Real(1e-30).toString() ; + EXPECT_EQ("1e+30", Real(1e+30).toString()) << Real(1e+30).toString() ; + EXPECT_EQ("1e-300", Real(1e-300).toString()) << Real(1e-300).toString() ; + + EXPECT_EQ("-1.0000000000000001e+300", Real(-1e+300).toString()) << Real(-1e+300).toString() ; + EXPECT_EQ("-1.0000000000000001e-30", Real(-1e-30).toString()) << Real(-1e-30).toString() ; + EXPECT_EQ("-1e+30", Real(-1e+30).toString()) << Real(-1e+30).toString() ; + EXPECT_EQ("-1e-300", Real(-1e-300).toString()) << Real(-1e-300).toString() ; + + EXPECT_EQ("1.1000000000000001e+300", Real(1.1e+300).toString()) << Real(1.1e+300).toString() ; + EXPECT_EQ("1.0999999999999999e-30", Real(1.1e-30).toString()) << Real(1.1e-30).toString() ; + EXPECT_EQ("1.1e+30", Real(1.1e+30).toString()) << Real(1.1e+30).toString() ; + EXPECT_EQ("1.1e-300", Real(1.1e-300).toString()) << Real(1.1e-300).toString() ; + + EXPECT_EQ("-1.1000000000000001e+300", Real(-1.1e+300).toString()) << Real(-1.1e+300).toString() ; + EXPECT_EQ("-1.0999999999999999e-30", Real(-1.1e-30).toString()) << Real(-1.1e-30).toString() ; + EXPECT_EQ("-1.1e+30", Real(-1.1e+30).toString()) << Real(-1.1e+30).toString() ; + EXPECT_EQ("-1.1e-300", Real(-1.1e-300).toString()) << Real(-1.1e-300).toString() ; + + } + { EXPECT_EQ("-Inf", Real::NegativeInfinity().toString(3)) ; diff --git a/tools/.env b/tools/.env index 6c97484a..bde38b4f 100644 --- a/tools/.env +++ b/tools/.env @@ -38,7 +38,7 @@ fi repository_name="openspacecollective" # image_version="$(echo ${version} | head -c 5)" -image_version="0.1.6" +image_version="0.1.7" image_name="${repository_name}/${project_name}:${image_version}" diff --git a/tools/ci/deploy.sh b/tools/ci/deploy.sh index 3fe3b8aa..ecb01663 100755 --- a/tools/ci/deploy.sh +++ b/tools/ci/deploy.sh @@ -16,7 +16,7 @@ development_directory="${project_directory}/tools/development" source "${project_directory}/tools/.env" -# Generate binaries +# Deploy Python bindings docker run \ --rm \ diff --git a/tools/development/docker/Dockerfile b/tools/development/docker/Dockerfile index 199c3843..0888b2a8 100644 --- a/tools/development/docker/Dockerfile +++ b/tools/development/docker/Dockerfile @@ -7,7 +7,7 @@ ################################################################################################################################################################ -FROM openspacecollective/library-base:0.1.4 +FROM openspacecollective/library-base:0.1.5 LABEL maintainer="lucas@loftorbital.com" @@ -30,19 +30,19 @@ RUN pushd /tmp > /dev/null \ && rm -rf /tmp/rapidjson \ && popd > /dev/null -## ordered-map [master] +## ordered-map [0.6.0] RUN pushd /tmp > /dev/null \ - && git clone https://github.com/Tessil/ordered-map.git \ + && git clone --branch v0.6.0 --depth 1 https://github.com/Tessil/ordered-map.git \ && cd ordered-map \ && cp -r ./include/tsl /usr/local/include \ && rm -rf /tmp/ordered-map \ && popd > /dev/null -## {fmt} [master] +## {fmt} [5.2.0] RUN pushd /tmp > /dev/null \ - && git clone https://github.com/fmtlib/fmt.git \ + && git clone --branch 5.2.0 --depth 1 https://github.com/fmtlib/fmt.git \ && cd fmt \ && mkdir build \ && cd build \ diff --git a/tools/python/old/README.md b/tools/python/old/README.md index 13c0cbf8..eb1bdb12 100644 --- a/tools/python/old/README.md +++ b/tools/python/old/README.md @@ -1,2 +1,2 @@ -Library :: Core :: Python Bindings -================================== \ No newline at end of file +Library ▸ Core ▸ Python Bindings +================================ \ No newline at end of file