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