diff --git a/algebra/TinyMatrix.hpp b/algebra/TinyMatrix.hpp new file mode 100644 index 0000000000000000000000000000000000000000..94bf1ad7d020913999f01e88af84e39e6dd5f97c --- /dev/null +++ b/algebra/TinyMatrix.hpp @@ -0,0 +1,249 @@ +#ifndef TINY_MATRIX_HPP +#define TINY_MATRIX_HPP + +#include <cassert> +#include <iostream> + +#include <Types.hpp> +#include <TinyVector.hpp> + +template <size_t N, typename T=double> +class TinyMatrix +{ +private: + T m_values[N*N]; + static_assert((N>0),"TinyMatrix size must be strictly positive"); + + KOKKOS_INLINE_FUNCTION + size_t _index(const size_t& i, const size_t& j) const + { + return std::move(i*N+j); + } + + void _unpackVariadicInput(const T& t) + { + m_values[N*N-1] = t; + } + + template <typename... Args> + void _unpackVariadicInput(const T& t, Args&&... args) + { + m_values[N*N-1-sizeof...(args)] = t; + this->_unpackVariadicInput(args...); + } + +public: + + KOKKOS_INLINE_FUNCTION + friend TinyMatrix operator*(const T& t, const TinyMatrix& A) + { + TinyMatrix tA; + for (size_t i=0; i<N*N; ++i) { + tA.m_values[i] = t * A.m_values[i]; + } + return std::move(tA); + } + + + KOKKOS_INLINE_FUNCTION + TinyMatrix operator*(const TinyMatrix& B) const + { + const TinyMatrix& A = *this; + TinyMatrix AB; + for (size_t i=0; i<N; ++i) { + for (size_t j=0; j<N; ++j) { + T sum = A(i,0)*B(0,j); + for (size_t k=1; k<N; ++k) { + sum += A(i,k)*B(k,j); + } + AB(i,j) = sum; + } + } + return std::move(AB); + } + + + KOKKOS_INLINE_FUNCTION + TinyVector<N,T> operator*(const TinyVector<N,T>& x) const + { + const TinyMatrix& A = *this; + TinyVector<N,T> Ax; + for (size_t i=0; i<N; ++i) { + T sum = A(i,0)*x[0]; + for (size_t j=1; j<N; ++j) { + sum += A(i,j)*x[j]; + } + Ax[i] = sum; + } + return std::move(Ax); + } + + + KOKKOS_INLINE_FUNCTION + friend std::ostream& operator<<(std::ostream& os, const TinyMatrix& A) + { + os << '['; + for (size_t i=0; i<N; ++i) { + os << '(' << A(i,0); + for (size_t j=1; j<N; ++j) { + os << ',' << A(i,j); + } + os << ')'; + } + os << ']'; + return os; + } + + KOKKOS_INLINE_FUNCTION + TinyMatrix operator+(const TinyMatrix& A) const + { + TinyMatrix sum; + for (size_t i=0; i<N*N; ++i) { + sum.m_values[i] = m_values[i]+A.m_values[i]; + } + return std::move(sum); + } + + KOKKOS_INLINE_FUNCTION + TinyMatrix operator-(const TinyMatrix& A) const + { + TinyMatrix difference; + for (size_t i=0; i<N*N; ++i) { + difference.m_values[i] = m_values[i]-A.m_values[i]; + } + return std::move(difference); + } + + KOKKOS_INLINE_FUNCTION + TinyMatrix& operator+=(const TinyMatrix& A) + { + for (size_t i=0; i<N*N; ++i) { + m_values[i] += A.m_values[i]; + } + return *this; + } + + KOKKOS_INLINE_FUNCTION + TinyMatrix& operator-=(const TinyMatrix& A) + { + for (size_t i=0; i<N*N; ++i) { + m_values[i] -= A.m_values[i]; + } + return *this; + } + + KOKKOS_INLINE_FUNCTION + T& operator()(const size_t& i, const size_t& j) + { + assert((i<N) and (j<N)); + return m_values[_index(i,j)]; + } + + KOKKOS_INLINE_FUNCTION + const T& operator()(const size_t& i, const size_t& j) const + { + assert((i<N) and (j<N)); + return m_values[_index(i,j)]; + } + + KOKKOS_INLINE_FUNCTION + TinyMatrix& operator=(const ZeroType& z) + { + static_assert(std::is_arithmetic<T>(),"Cannot assign 'zero' value for non-arithmetic types"); + for (size_t i=0; i<N*N; ++i) { + m_values[i] = 0; + } + return *this; + } + + KOKKOS_INLINE_FUNCTION + TinyMatrix& operator=(const IdentityType& I) + { + static_assert(std::is_arithmetic<T>(),"Cannot assign 'identity' value for non-arithmetic types"); + for (size_t i=0; i<N; ++i) { + for (size_t j=0; j<N; ++j) { + m_values[_index(i,j)] = (i==j) ? 1 : 0; + } + } + return *this; + } + + KOKKOS_INLINE_FUNCTION + const TinyMatrix& operator=(const TinyMatrix& A) + { + for (size_t i=0; i<N*N; ++i) { + m_values[i] = A.m_values[i]; + } + return *this; + } + + KOKKOS_INLINE_FUNCTION + TinyMatrix& operator=(TinyMatrix&& A) = default; + + template <typename... Args> + KOKKOS_INLINE_FUNCTION + TinyMatrix(const T& t, Args&&... args) + { + static_assert(sizeof...(args)==N*N-1, "wrong number of parameters"); + this->_unpackVariadicInput(t, args...); + } + + KOKKOS_INLINE_FUNCTION + TinyMatrix() + { + ; + } + + KOKKOS_INLINE_FUNCTION + TinyMatrix(const ZeroType& z) + { + static_assert(std::is_arithmetic<T>(),"Cannot construct from 'zero' value for non-arithmetic types"); + for (size_t i=0; i<N*N; ++i) { + m_values[i] = 0; + } + } + + KOKKOS_INLINE_FUNCTION + TinyMatrix(const IdentityType& I) + { + static_assert(std::is_arithmetic<T>(),"Cannot construct from 'identity' value for non-arithmetic types"); + for (size_t i=0; i<N; ++i) { + for (size_t j=0; j<N; ++j) { + m_values[_index(i,j)] = (i==j) ? 1 : 0; + } + } + } + + KOKKOS_INLINE_FUNCTION + TinyMatrix(const TinyMatrix& A) + { + for (size_t i=0; i<N*N; ++i) { + m_values[i] = A.m_values[i]; + } + } + + KOKKOS_INLINE_FUNCTION + TinyMatrix(TinyMatrix&& A) = default; + + KOKKOS_INLINE_FUNCTION + ~TinyMatrix() + { + ; + } +}; + +template <size_t N, typename T> +KOKKOS_INLINE_FUNCTION +TinyMatrix<N,T> tensorProduct(const TinyVector<N,T>& x, + const TinyVector<N,T>& y) +{ + TinyMatrix<N,T> A; + for (size_t i=0; i<N; ++i) { + for (size_t j=0; j<N; ++j) { + A(i,j) = x[i]*y[j]; + } + } + return std::move(A); +} + +#endif // TINYMATRIX_HPP diff --git a/algebra/TinyVector.hpp b/algebra/TinyVector.hpp index 4dec9f1a80c2e0d3bfb81158352eab80e60005d4..9b2ca1619f0de8e2b811d41ae7be7f711c68a436 100644 --- a/algebra/TinyVector.hpp +++ b/algebra/TinyVector.hpp @@ -4,6 +4,8 @@ #include <cassert> #include <iostream> +#include <Types.hpp> + template <size_t N, typename T=double> class TinyVector { @@ -11,9 +13,21 @@ private: T m_values[N]; static_assert((N>0),"TinyVector size must be strictly positive"); + void _unpackVariadicInput(const T& t) + { + m_values[N-1] = t; + } + + template <typename... Args> + void _unpackVariadicInput(const T& t, Args&&... args) + { + m_values[N-1-sizeof...(args)] = t; + this->_unpackVariadicInput(args...); + } + public: KOKKOS_INLINE_FUNCTION - T operator,(const TinyVector& v) + T operator,(const TinyVector& v) const { T t = m_values[0]*v.m_values[0]; for (size_t i=1; i<N; ++i) { @@ -81,15 +95,6 @@ public: return *this; } - KOKKOS_INLINE_FUNCTION - TinyVector& operator=(const T& t) - { - for (size_t i=0; i<N; ++i) { - m_values[i] = t; - } - return *this; - } - KOKKOS_INLINE_FUNCTION T& operator[](const size_t& i) { @@ -104,6 +109,16 @@ public: return m_values[i]; } + KOKKOS_INLINE_FUNCTION + TinyVector& operator=(const ZeroType& z) + { + static_assert(std::is_arithmetic<T>(),"Cannot assign 'zero' value for non-arithmetic types"); + for (size_t i=0; i<N; ++i) { + m_values[i] = 0; + } + return *this; + } + KOKKOS_INLINE_FUNCTION const TinyVector& operator=(const TinyVector& v) { @@ -116,6 +131,14 @@ public: KOKKOS_INLINE_FUNCTION TinyVector& operator=(TinyVector&& v) = default; + template <typename... Args> + KOKKOS_INLINE_FUNCTION + TinyVector(const T& t, Args&&... args) + { + static_assert(sizeof...(args)==N-1, "wrong number of parameters"); + this->_unpackVariadicInput(t, args...); + } + KOKKOS_INLINE_FUNCTION TinyVector() { @@ -123,10 +146,11 @@ public: } KOKKOS_INLINE_FUNCTION - TinyVector(const T& t) + TinyVector(const ZeroType& z) { + static_assert(std::is_arithmetic<T>(),"Cannot construct from 'zero' value for non-arithmetic types"); for (size_t i=0; i<N; ++i) { - m_values[i] = t; + m_values[i] = 0; } } diff --git a/experimental/AcousticSolver.cpp b/experimental/AcousticSolver.cpp index 3088ff7176c03d46e6e429cb0b503123d0ecd66f..80ed8310e81e6c19e682d889b344953f3de418bf 100644 --- a/experimental/AcousticSolver.cpp +++ b/experimental/AcousticSolver.cpp @@ -1,8 +1,6 @@ #include <AcousticSolver.hpp> #include <rang.hpp> -#include <memory> - #include <BlockPerfectGas.hpp> typedef const double my_double; @@ -48,11 +46,10 @@ const Kokkos::View<const double*> AcousticSolver::computeRhoCj(const Kokkos::View<const double*>& rhoj, const Kokkos::View<const double*>& cj) { - Kokkos::View<double*> rhocj("rho_c", m_nj); Kokkos::parallel_for(m_nj, KOKKOS_LAMBDA(const int& j) { - rhocj[j] = rhoj[j]*cj[j]; + m_rhocj[j] = rhoj[j]*cj[j]; }); - return rhocj; + return m_rhocj; } KOKKOS_INLINE_FUNCTION @@ -60,14 +57,13 @@ const Kokkos::View<const double*[2]> AcousticSolver::computeAjr(const Kokkos::View<const double*>& rhocj, const Kokkos::View<const double*[2]>& Cjr) { - Kokkos::View<double*[2]> Ajr("Ajr", m_nj); Kokkos::parallel_for(m_nj, KOKKOS_LAMBDA(const int& j) { for (int r=0; r<2; ++r) { - Ajr(j,r) = rhocj(j)*Cjr(j,r)*Cjr(j,r); + m_Ajr(j,r) = rhocj(j)*Cjr(j,r)*Cjr(j,r); } }); - return Ajr; + return m_Ajr; } KOKKOS_INLINE_FUNCTION @@ -77,7 +73,6 @@ AcousticSolver::computeAr(const Kokkos::View<const double*[2]>& Ajr, const Kokkos::View<const int*[2]>& node_cell_local_node, const Kokkos::View<const int*>& node_nb_cells) { - Kokkos::View<double*> Ar("Ar", m_nr); Kokkos::parallel_for(m_nr, KOKKOS_LAMBDA(const int& r) { double sum = 0; for (int j=0; j<node_nb_cells(r); ++j) { @@ -85,10 +80,10 @@ AcousticSolver::computeAr(const Kokkos::View<const double*[2]>& Ajr, const int R = node_cell_local_node(r,j); sum += Ajr(J,R); } - Ar(r) = sum; + m_Ar(r) = sum; }); - return Ar; + return m_Ar; } KOKKOS_INLINE_FUNCTION @@ -101,18 +96,17 @@ AcousticSolver::computeBr(const Kokkos::View<const double*[2]>& Ajr, const Kokkos::View<const int*[2]>& node_cell_local_node, const Kokkos::View<const int*>& node_nb_cells) { - Kokkos::View<double*> br("br", m_nr); Kokkos::parallel_for(m_nr, KOKKOS_LAMBDA(const int& r) { double sum = 0; for (int j=0; j<node_nb_cells(r); ++j) { - const int J = node_cells(r,j); - const int R = node_cell_local_node(r,j); - sum += Ajr(J,R)*uj(J) + Cjr(J,R)*pj[J]; + const int J = node_cells(r,j); + const int R = node_cell_local_node(r,j); + sum += Ajr(J,R)*uj(J) + Cjr(J,R)*pj(J); } - br(r) = sum; + m_br(r) = sum; }); - return br; + return m_br; } KOKKOS_INLINE_FUNCTION @@ -120,15 +114,15 @@ Kokkos::View<double*> AcousticSolver::computeUr(const Kokkos::View<const double*>& Ar, const Kokkos::View<const double*>& br) { - const Kokkos::View<const double*> invAr = inverse(Ar); - Kokkos::View<double*> ur("ur", m_nr); + inverse(Ar, m_inv_Ar); + const Kokkos::View<const double*> invAr = m_inv_Ar; Kokkos::parallel_for(m_nr, KOKKOS_LAMBDA(const int& r) { - ur[r]=br(r)*invAr(r); + m_ur[r]=br(r)*invAr(r); }); - ur[0]=0; - ur[m_nr-1]=0; + m_ur[0]=0; + m_ur[m_nr-1]=0; - return ur; + return m_ur; } KOKKOS_INLINE_FUNCTION @@ -140,15 +134,13 @@ AcousticSolver::computeFjr(const Kokkos::View<const double*[2]>& Ajr, const Kokkos::View<const double*>& pj, const Kokkos::View<const int*[2]>& cell_nodes) { - Kokkos::View<double*[2]> Fjr("Fjr", m_nj); - Kokkos::parallel_for(m_nj, KOKKOS_LAMBDA(const int& j) { for (int r=0; r<2; ++r) { - Fjr(j,r) = Ajr(j,r)*(uj(j)-ur(cell_nodes(j,r)))+Cjr(j,r)*pj(j); + m_Fjr(j,r) = Ajr(j,r)*(uj(j)-ur(cell_nodes(j,r)))+Cjr(j,r)*pj(j); } }); - return Fjr; + return m_Fjr; } KOKKOS_INLINE_FUNCTION @@ -156,29 +148,24 @@ double AcousticSolver:: acoustic_dt(const Kokkos::View<const double*>& Vj, const Kokkos::View<const double*>& cj) const { - double dt = std::numeric_limits<double>::max(); - - Kokkos::View<double*> Vj_cj("Vj_cj", m_nj); - Kokkos::parallel_for(m_nj, KOKKOS_LAMBDA(const int& j){ - Vj_cj[j] = Vj[j]/cj[j]; + m_Vj_over_cj[j] = Vj[j]/cj[j]; }); - Kokkos::parallel_reduce(m_nj, ReduceMin(Vj_cj), dt); + double dt = std::numeric_limits<double>::max(); + Kokkos::parallel_reduce(m_nj, ReduceMin(m_Vj_over_cj), dt); return dt; } KOKKOS_INLINE_FUNCTION -const Kokkos::View<const double*> -AcousticSolver::inverse(const Kokkos::View<const double*>& x) const +void +AcousticSolver::inverse(const Kokkos::View<const double*>& x, + Kokkos::View<double*>& inv_x) const { - Kokkos::View<double*> inv_x("inv_x", x.size()); Kokkos::parallel_for(x.size(), KOKKOS_LAMBDA(const int& r) { inv_x(r) = 1./x(r); }); - - return inv_x; } KOKKOS_INLINE_FUNCTION @@ -210,7 +197,15 @@ void AcousticSolver::computeExplicitFluxes(const Kokkos::View<const double*>& xr AcousticSolver::AcousticSolver(const long int& nj) : m_nj(nj), - m_nr(nj+1) + m_nr(nj+1), + m_br("br", m_nr), + m_Ajr("Ajr", m_nj), + m_Ar("Ar", m_nr), + m_inv_Ar("inv_Ar", m_nr), + m_Fjr("Fjr", m_nj), + m_ur("ur", m_nr), + m_rhocj("rho_c", m_nj), + m_Vj_over_cj("Vj_over_cj", m_nj) { Kokkos::View<double*> xj("xj",m_nj); Kokkos::View<double*> rhoj("rhoj",m_nj); @@ -276,20 +271,20 @@ AcousticSolver::AcousticSolver(const long int& nj) }); Kokkos::parallel_for(nj, KOKKOS_LAMBDA(const int& j){ - if (xj[j]<0.5) { - rhoj[j]=1; - } else { - rhoj[j]=0.125; - } - }); + if (xj[j]<0.5) { + rhoj[j]=1; + } else { + rhoj[j]=0.125; + } + }); Kokkos::parallel_for(nj, KOKKOS_LAMBDA(const int& j){ - if (xj[j]<0.5) { - pj[j]=1; - } else { - pj[j]=0.1; - } - }); + if (xj[j]<0.5) { + pj[j]=1; + } else { + pj[j]=0.1; + } + }); Kokkos::parallel_for(nj, KOKKOS_LAMBDA(const int& j){ gammaj[j] = 1.4; @@ -301,13 +296,14 @@ AcousticSolver::AcousticSolver(const long int& nj) Kokkos::parallel_for(nj, KOKKOS_LAMBDA(const int& j){ Ej[j] = ej[j]+0.5*uj[j]*uj[j]; - }); + }); Kokkos::parallel_for(nj, KOKKOS_LAMBDA(const int& j){ - mj[j] = rhoj[j] * Vj[j]; - }); + mj[j] = rhoj[j] * Vj[j]; + }); - const Kokkos::View<const double*> inv_mj=inverse(mj); + Kokkos::View<double*> inv_mj("inv_mj",m_nj); + inverse(mj, inv_mj); const double tmax=0.2; double t=0; @@ -329,14 +325,14 @@ AcousticSolver::AcousticSolver(const long int& nj) dt=tmax-t; t=tmax; } - - Kokkos::View<double*> ur; - Kokkos::View<double*[2]> Fjr; - + computeExplicitFluxes(xr, xj, rhoj, uj, pj, cj, Vj, Cjr, cell_nodes, node_cells, node_nb_cells, node_cell_local_node, - ur, Fjr); + m_ur, m_Fjr); + + const Kokkos::View<const double*[2]> Fjr = m_Fjr; + const Kokkos::View<const double*> ur = m_ur; Kokkos::parallel_for(nj, KOKKOS_LAMBDA(const int& j){ double momentum_fluxes = 0; @@ -359,8 +355,8 @@ AcousticSolver::AcousticSolver(const long int& nj) }); Kokkos::parallel_for(nj, KOKKOS_LAMBDA(const int& j){ - xj[j] = 0.5*(xr[j]+xr[j+1]); - Vj[j] = xr[j+1]-xr[j]; + xj[j] = 0.5*(xr[j]+xr[j+1]); + Vj[j] = xr[j+1]-xr[j]; }); Kokkos::parallel_for(nj, KOKKOS_LAMBDA(const int& j){ diff --git a/experimental/AcousticSolver.hpp b/experimental/AcousticSolver.hpp index d8429d5acc023acea9ef6930c8de1306f0bc1370..5ac400a9db22b4a96bcbabdf90fdd26d53eaa2ba 100644 --- a/experimental/AcousticSolver.hpp +++ b/experimental/AcousticSolver.hpp @@ -6,7 +6,6 @@ class AcousticSolver { private: - inline const Kokkos::View<const double*> computeRhoCj(const Kokkos::View<const double*>& rhoj, const Kokkos::View<const double*>& cj); @@ -42,8 +41,8 @@ private: const Kokkos::View<const double*>& pj, const Kokkos::View<const int*[2]>& cell_nodes); - const Kokkos::View<const double*> - inverse(const Kokkos::View<const double*>& x) const; + void inverse(const Kokkos::View<const double*>& x, + Kokkos::View<double*>& inv_x) const; inline double acoustic_dt(const Kokkos::View<const double*>& Vj, const Kokkos::View<const double*>& cj) const; @@ -68,6 +67,16 @@ private: const long int m_nj; const long int m_nr; + Kokkos::View<double*> m_br; + Kokkos::View<double*[2]> m_Ajr; + Kokkos::View<double*> m_Ar; + Kokkos::View<double*> m_inv_Ar; + Kokkos::View<double*[2]> m_Fjr; + Kokkos::View<double*> m_ur; + Kokkos::View<double*> m_rhocj; + Kokkos::View<double*> m_Vj_over_cj; + + public: AcousticSolver(const long int& nj); }; diff --git a/experimental/AcousticSolverTest.cpp b/experimental/AcousticSolverTest.cpp index 5c1e190b8f06163cb952db133523067d9b142bca..06b0382dea45741a25665907ba009cc7af311015 100644 --- a/experimental/AcousticSolverTest.cpp +++ b/experimental/AcousticSolverTest.cpp @@ -1,8 +1,6 @@ #include <AcousticSolverTest.hpp> #include <rang.hpp> -#include <memory> - #include <BlockPerfectGas.hpp> typedef const double my_double; @@ -55,13 +53,13 @@ AcousticSolverTest::computeRhoCj(const Kokkos::View<const double*>& rhoj, } KOKKOS_INLINE_FUNCTION -const Kokkos::View<const double*[2]> +const Kokkos::View<const Rdd*[2]> AcousticSolverTest::computeAjr(const Kokkos::View<const double*>& rhocj, - const Kokkos::View<const double*[2]>& Cjr) + const Kokkos::View<const Rd*[2]>& Cjr) { Kokkos::parallel_for(m_nj, KOKKOS_LAMBDA(const int& j) { for (int r=0; r<2; ++r) { - m_Ajr(j,r) = rhocj(j)*Cjr(j,r)*Cjr(j,r); + m_Ajr(j,r) = tensorProduct(rhocj(j)*Cjr(j,r), Cjr(j,r)); } }); @@ -69,14 +67,14 @@ AcousticSolverTest::computeAjr(const Kokkos::View<const double*>& rhocj, } KOKKOS_INLINE_FUNCTION -const Kokkos::View<const double*> -AcousticSolverTest::computeAr(const Kokkos::View<const double*[2]>& Ajr, +const Kokkos::View<const Rdd*> +AcousticSolverTest::computeAr(const Kokkos::View<const Rdd*[2]>& Ajr, const Kokkos::View<const int*[2]>& node_cells, const Kokkos::View<const int*[2]>& node_cell_local_node, const Kokkos::View<const int*>& node_nb_cells) { Kokkos::parallel_for(m_nr, KOKKOS_LAMBDA(const int& r) { - double sum = 0; + Rdd sum = zero; for (int j=0; j<node_nb_cells(r); ++j) { const int J = node_cells(r,j); const int R = node_cell_local_node(r,j); @@ -89,56 +87,56 @@ AcousticSolverTest::computeAr(const Kokkos::View<const double*[2]>& Ajr, } KOKKOS_INLINE_FUNCTION -const Kokkos::View<const double*> -AcousticSolverTest::computeBr(const Kokkos::View<const double*[2]>& Ajr, - const Kokkos::View<const double*[2]>& Cjr, - const Kokkos::View<const double*>& uj, +const Kokkos::View<const Rd*> +AcousticSolverTest::computeBr(const Kokkos::View<const Rdd*[2]>& Ajr, + const Kokkos::View<const Rd*[2]>& Cjr, + const Kokkos::View<const Rd*>& uj, const Kokkos::View<const double*>& pj, const Kokkos::View<const int*[2]>& node_cells, const Kokkos::View<const int*[2]>& node_cell_local_node, const Kokkos::View<const int*>& node_nb_cells) { Kokkos::parallel_for(m_nr, KOKKOS_LAMBDA(const int& r) { - double sum = 0; + Rd& br = m_br(r); + br = zero; for (int j=0; j<node_nb_cells(r); ++j) { const int J = node_cells(r,j); const int R = node_cell_local_node(r,j); - sum += Ajr(J,R)*uj(J) + Cjr(J,R)*pj(J); + br += Ajr(J,R)*uj(J) + pj(J)*Cjr(J,R); } - m_br(r) = sum; }); return m_br; } KOKKOS_INLINE_FUNCTION -Kokkos::View<double*> -AcousticSolverTest::computeUr(const Kokkos::View<const double*>& Ar, - const Kokkos::View<const double*>& br) +Kokkos::View<Rd*> +AcousticSolverTest::computeUr(const Kokkos::View<const Rdd*>& Ar, + const Kokkos::View<const Rd*>& br) { inverse(Ar, m_inv_Ar); - const Kokkos::View<const double*> invAr = m_inv_Ar; + const Kokkos::View<const Rdd*> invAr = m_inv_Ar; Kokkos::parallel_for(m_nr, KOKKOS_LAMBDA(const int& r) { - m_ur[r]=br(r)*invAr(r); + m_ur[r]=invAr(r)*br(r); }); - m_ur[0]=0; - m_ur[m_nr-1]=0; + m_ur[0]=zero; + m_ur[m_nr-1]=zero; return m_ur; } KOKKOS_INLINE_FUNCTION -Kokkos::View<double*[2]> -AcousticSolverTest::computeFjr(const Kokkos::View<const double*[2]>& Ajr, - const Kokkos::View<const double*>& ur, - const Kokkos::View<const double*[2]>& Cjr, - const Kokkos::View<const double*>& uj, +Kokkos::View<Rd*[2]> +AcousticSolverTest::computeFjr(const Kokkos::View<const Rdd*[2]>& Ajr, + const Kokkos::View<const Rd*>& ur, + const Kokkos::View<const Rd*[2]>& Cjr, + const Kokkos::View<const Rd*>& uj, const Kokkos::View<const double*>& pj, const Kokkos::View<const int*[2]>& cell_nodes) { Kokkos::parallel_for(m_nj, KOKKOS_LAMBDA(const int& j) { for (int r=0; r<2; ++r) { - m_Fjr(j,r) = Ajr(j,r)*(uj(j)-ur(cell_nodes(j,r)))+Cjr(j,r)*pj(j); + m_Fjr(j,r) = Ajr(j,r)*(uj(j)-ur(cell_nodes(j,r)))+pj(j)*Cjr(j,r); } }); @@ -171,26 +169,37 @@ AcousticSolverTest::inverse(const Kokkos::View<const double*>& x, } KOKKOS_INLINE_FUNCTION -void AcousticSolverTest::computeExplicitFluxes(const Kokkos::View<const double*>& xr, - const Kokkos::View<const double*>& xj, +void +AcousticSolverTest::inverse(const Kokkos::View<const Rdd*>& A, + Kokkos::View<Rdd*>& inv_A) const +{ + Kokkos::parallel_for(A.size(), KOKKOS_LAMBDA(const int& r) { + inv_A(r) = Rdd{1./(A(r)(0,0))}; + }); +} + + +KOKKOS_INLINE_FUNCTION +void AcousticSolverTest::computeExplicitFluxes(const Kokkos::View<const Rd*>& xr, + const Kokkos::View<const Rd*>& xj, const Kokkos::View<const double*>& rhoj, - const Kokkos::View<const double*>& uj, + const Kokkos::View<const Rd*>& uj, const Kokkos::View<const double*>& pj, const Kokkos::View<const double*>& cj, const Kokkos::View<const double*>& Vj, - const Kokkos::View<const double*[2]>& Cjr, + const Kokkos::View<const Rd*[2]>& Cjr, const Kokkos::View<const int*[2]>& cell_nodes, const Kokkos::View<const int*[2]>& node_cells, const Kokkos::View<const int*>& node_nb_cells, const Kokkos::View<const int*[2]>& node_cell_local_node, - Kokkos::View<double*>& ur, - Kokkos::View<double*[2]>& Fjr) + Kokkos::View<Rd*>& ur, + Kokkos::View<Rd*[2]>& Fjr) { const Kokkos::View<const double*> rhocj = computeRhoCj(rhoj, cj); - const Kokkos::View<const double*[2]> Ajr = computeAjr(rhocj, Cjr); + const Kokkos::View<const Rdd*[2]> Ajr = computeAjr(rhocj, Cjr); - const Kokkos::View<const double*> Ar = computeAr(Ajr, node_cells, node_cell_local_node, node_nb_cells); - const Kokkos::View<const double*> br = computeBr(Ajr, Cjr, uj, pj, + const Kokkos::View<const Rdd*> Ar = computeAr(Ajr, node_cells, node_cell_local_node, node_nb_cells); + const Kokkos::View<const Rd*> br = computeBr(Ajr, Cjr, uj, pj, node_cells, node_cell_local_node, node_nb_cells); ur = computeUr(Ar, br); @@ -209,10 +218,10 @@ AcousticSolverTest::AcousticSolverTest(const long int& nj) m_rhocj("rho_c", m_nj), m_Vj_over_cj("Vj_over_cj", m_nj) { - Kokkos::View<double*> xj("xj",m_nj); + Kokkos::View<Rd*> xj("xj",m_nj); Kokkos::View<double*> rhoj("rhoj",m_nj); - Kokkos::View<double*> uj("uj",m_nj); + Kokkos::View<Rd*> uj("uj",m_nj); Kokkos::View<double*> Ej("Ej",m_nj); Kokkos::View<double*> ej("ej",m_nj); @@ -222,12 +231,12 @@ AcousticSolverTest::AcousticSolverTest(const long int& nj) Kokkos::View<double*> cj("cj",m_nj); Kokkos::View<double*> mj("mj",m_nj); - Kokkos::View<double*> xr("xr", m_nr); + Kokkos::View<Rd*> xr("xr", m_nr); const double delta_x = 1./m_nj; Kokkos::parallel_for(m_nr, KOKKOS_LAMBDA(const int& r){ - xr[r] = r*delta_x; + xr[r][0] = r*delta_x; }); Kokkos::View<int*[2]> cell_nodes("cell_nodes",m_nj,2); @@ -268,12 +277,19 @@ AcousticSolverTest::AcousticSolverTest(const long int& nj) xj[j] = 0.5*(xr[cell_nodes(j,0)]+xr[cell_nodes(j,1)]); }); + Kokkos::View<Rd*[2]> Cjr("Cjr",m_nj); + Kokkos::parallel_for(nj, KOKKOS_LAMBDA(const int& j) { + Cjr(j,0)=-1; + Cjr(j,1)= 1; + }); + Kokkos::parallel_for(nj, KOKKOS_LAMBDA(const int& j){ - Vj[j] = xr[cell_nodes(j,1)]-xr[cell_nodes(j,0)]; + Vj[j] = (xr[cell_nodes(j,1)], Cjr(j,1)) + + (xr[cell_nodes(j,0)], Cjr(j,0)); }); Kokkos::parallel_for(nj, KOKKOS_LAMBDA(const int& j){ - if (xj[j]<0.5) { + if (xj[j][0]<0.5) { rhoj[j]=1; } else { rhoj[j]=0.125; @@ -281,13 +297,17 @@ AcousticSolverTest::AcousticSolverTest(const long int& nj) }); Kokkos::parallel_for(nj, KOKKOS_LAMBDA(const int& j){ - if (xj[j]<0.5) { + if (xj[j][0]<0.5) { pj[j]=1; } else { pj[j]=0.1; } }); + Kokkos::parallel_for(nj, KOKKOS_LAMBDA(const int& j){ + uj[j] = zero; + }); + Kokkos::parallel_for(nj, KOKKOS_LAMBDA(const int& j){ gammaj[j] = 1.4; }); @@ -297,7 +317,7 @@ AcousticSolverTest::AcousticSolverTest(const long int& nj) block_eos.updateEandCFromRhoP(); Kokkos::parallel_for(nj, KOKKOS_LAMBDA(const int& j){ - Ej[j] = ej[j]+0.5*uj[j]*uj[j]; + Ej[j] = ej[j]+0.5*(uj[j],uj[j]); }); Kokkos::parallel_for(nj, KOKKOS_LAMBDA(const int& j){ @@ -313,12 +333,6 @@ AcousticSolverTest::AcousticSolverTest(const long int& nj) int itermax=std::numeric_limits<int>::max(); int iteration=0; - Kokkos::View<double*[2]> Cjr("Cjr",m_nj); - Kokkos::parallel_for(nj, KOKKOS_LAMBDA(const int& j) { - Cjr(j,0)=-1; - Cjr(j,1)= 1; - }); - while((t<tmax) and (iteration<itermax)) { double dt = 0.4*acoustic_dt(Vj, cj); if (t+dt<tmax) { @@ -333,23 +347,23 @@ AcousticSolverTest::AcousticSolverTest(const long int& nj) cell_nodes, node_cells, node_nb_cells, node_cell_local_node, m_ur, m_Fjr); - const Kokkos::View<const double*[2]> Fjr = m_Fjr; - const Kokkos::View<const double*> ur = m_ur; + const Kokkos::View<const Rd*[2]> Fjr = m_Fjr; + const Kokkos::View<const Rd*> ur = m_ur; - Kokkos::parallel_for(nj, KOKKOS_LAMBDA(const int& j){ - double momentum_fluxes = 0; + Kokkos::parallel_for(nj, KOKKOS_LAMBDA(const int& j) { + Rd momentum_fluxes = zero; double energy_fluxes = 0; for (int R=0; R<2; ++R) { const int r=cell_nodes(j,R); - momentum_fluxes += Fjr(j,R); - energy_fluxes += Fjr(j,R)*ur[r]; + momentum_fluxes += Fjr(j,R); + energy_fluxes += ((Fjr(j,R), ur[r])); } - uj[j] -= dt*inv_mj[j]*(momentum_fluxes); - Ej[j] -= dt*inv_mj[j]*(energy_fluxes); + uj[j] -= (dt*inv_mj[j]) * momentum_fluxes; + Ej[j] -= (dt*inv_mj[j]) * energy_fluxes; }); Kokkos::parallel_for(nj, KOKKOS_LAMBDA(const int& j) { - ej[j] = Ej[j] - 0.5 * uj[j]*uj[j]; + ej[j] = Ej[j] - 0.5 * (uj[j],uj[j]); }); Kokkos::parallel_for(m_nr, KOKKOS_LAMBDA(const int& r){ @@ -358,9 +372,13 @@ AcousticSolverTest::AcousticSolverTest(const long int& nj) Kokkos::parallel_for(nj, KOKKOS_LAMBDA(const int& j){ xj[j] = 0.5*(xr[j]+xr[j+1]); - Vj[j] = xr[j+1]-xr[j]; }); + Kokkos::parallel_for(nj, KOKKOS_LAMBDA(const int& j){ + Vj[j] = (xr[cell_nodes(j,1)], Cjr(j,1)) + + (xr[cell_nodes(j,0)], Cjr(j,0)); + }); + Kokkos::parallel_for(nj, KOKKOS_LAMBDA(const int& j){ rhoj[j] = mj[j]/Vj[j]; }); @@ -370,12 +388,12 @@ AcousticSolverTest::AcousticSolverTest(const long int& nj) ++iteration; } - // { - // std::ofstream fout("rho"); - // for (int j=0; j<nj; ++j) { - // fout << xj[j] << ' ' << rhoj[j] << '\n'; - // } - // } + { + std::ofstream fout("rho"); + for (int j=0; j<nj; ++j) { + fout << xj[j][0] << ' ' << rhoj[j] << '\n'; + } + } std::cout << "* " << rang::style::underline << "Final time" << rang::style::reset << ": " << rang::fgB::green << t << rang::fg::reset << " (" << iteration << " iterations)\n"; diff --git a/experimental/AcousticSolverTest.hpp b/experimental/AcousticSolverTest.hpp index f1281635550cb9323452c5d4cb85e688b6216704..209a18747bb42eb467e6a992faaf8f64e706f856 100644 --- a/experimental/AcousticSolverTest.hpp +++ b/experimental/AcousticSolverTest.hpp @@ -3,76 +3,88 @@ #include <Kokkos_Core.hpp> +#include <TinyVector.hpp> +#include <TinyMatrix.hpp> + +constexpr static size_t dimension = 1; + +typedef TinyVector<dimension> Rd; +typedef TinyMatrix<dimension> Rdd; + class AcousticSolverTest { private: + inline const Kokkos::View<const double*> computeRhoCj(const Kokkos::View<const double*>& rhoj, const Kokkos::View<const double*>& cj); - inline const Kokkos::View<const double*[2]> + inline const Kokkos::View<const Rdd*[2]> computeAjr(const Kokkos::View<const double*>& rhocj, - const Kokkos::View<const double*[2]>& Cjr); + const Kokkos::View<const Rd*[2]>& Cjr); - inline const Kokkos::View<const double*> - computeAr(const Kokkos::View<const double*[2]>& Ajr, + inline const Kokkos::View<const Rdd*> + computeAr(const Kokkos::View<const Rdd*[2]>& Ajr, const Kokkos::View<const int*[2]>& node_cells, const Kokkos::View<const int*[2]>& node_cell_local_node, const Kokkos::View<const int*>& node_nb_cells); - inline const Kokkos::View<const double*> - computeBr(const Kokkos::View<const double*[2]>& Ajr, - const Kokkos::View<const double*[2]>& Cjr, - const Kokkos::View<const double*>& uj, + inline const Kokkos::View<const Rd*> + computeBr(const Kokkos::View<const Rdd*[2]>& Ajr, + const Kokkos::View<const Rd*[2]>& Cjr, + const Kokkos::View<const Rd*>& uj, const Kokkos::View<const double*>& pj, const Kokkos::View<const int*[2]>& node_cells, const Kokkos::View<const int*[2]>& node_cell_local_node, const Kokkos::View<const int*>& node_nb_cells); - Kokkos::View<double*> - computeUr(const Kokkos::View<const double*>& Ar, - const Kokkos::View<const double*>& br); + Kokkos::View<Rd*> + computeUr(const Kokkos::View<const Rdd*>& Ar, + const Kokkos::View<const Rd*>& br); - Kokkos::View<double*[2]> - computeFjr(const Kokkos::View<const double*[2]>& Ajr, - const Kokkos::View<const double*>& ur, - const Kokkos::View<const double*[2]>& Cjr, - const Kokkos::View<const double*>& uj, + Kokkos::View<Rd*[2]> + computeFjr(const Kokkos::View<const Rdd*[2]>& Ajr, + const Kokkos::View<const Rd*>& ur, + const Kokkos::View<const Rd*[2]>& Cjr, + const Kokkos::View<const Rd*>& uj, const Kokkos::View<const double*>& pj, const Kokkos::View<const int*[2]>& cell_nodes); + void inverse(const Kokkos::View<const Rdd*>& A, + Kokkos::View<Rdd*>& inv_A) const; + void inverse(const Kokkos::View<const double*>& x, Kokkos::View<double*>& inv_x) const; inline double acoustic_dt(const Kokkos::View<const double*>& Vj, const Kokkos::View<const double*>& cj) const; - inline void computeExplicitFluxes(const Kokkos::View<const double*>& xr, - const Kokkos::View<const double*>& xj, + inline void computeExplicitFluxes(const Kokkos::View<const Rd*>& xr, + const Kokkos::View<const Rd*>& xj, const Kokkos::View<const double*>& rhoj, - const Kokkos::View<const double*>& uj, + const Kokkos::View<const Rd*>& uj, const Kokkos::View<const double*>& pj, const Kokkos::View<const double*>& cj, const Kokkos::View<const double*>& Vj, - const Kokkos::View<const double*[2]>& Cjr, + const Kokkos::View<const Rd*[2]>& Cjr, const Kokkos::View<const int*[2]>& cell_nodes, const Kokkos::View<const int*[2]>& node_cells, const Kokkos::View<const int*>& node_nb_cells, const Kokkos::View<const int*[2]>& node_cell_local_node, - Kokkos::View<double*>& ur, - Kokkos::View<double*[2]>& Fjr); + Kokkos::View<Rd*>& ur, + Kokkos::View<Rd*[2]>& Fjr); struct ReduceMin; - const long int m_nj; - const long int m_nr; + const size_t m_nj; + const size_t m_nr; - Kokkos::View<double*> m_br; - Kokkos::View<double*[2]> m_Ajr; - Kokkos::View<double*> m_Ar; - Kokkos::View<double*> m_inv_Ar; - Kokkos::View<double*[2]> m_Fjr; - Kokkos::View<double*> m_ur; + Kokkos::View<Rd*> m_br; + Kokkos::View<Rdd*[2]> m_Ajr; + Kokkos::View<Rdd*> m_Ar; + Kokkos::View<Rdd*> m_inv_Ar; + Kokkos::View<Rd*[2]> m_Fjr; + Kokkos::View<Rd*> m_ur; Kokkos::View<double*> m_rhocj; Kokkos::View<double*> m_Vj_over_cj; diff --git a/experimental/AcousticSolverWithMesh.hpp b/experimental/AcousticSolverWithMesh.hpp new file mode 100644 index 0000000000000000000000000000000000000000..e5e7839029953e105d23117bcf811ee5a4bfdc0c --- /dev/null +++ b/experimental/AcousticSolverWithMesh.hpp @@ -0,0 +1,326 @@ +#ifndef ACOUSTIC_SOLVER_WITH_MESH_HPP +#define ACOUSTIC_SOLVER_WITH_MESH_HPP + +#include <Kokkos_Core.hpp> + +#include <rang.hpp> +#include <BlockPerfectGas.hpp> + +#include <TinyVector.hpp> +#include <TinyMatrix.hpp> +#include <Mesh.hpp> +#include <MeshData.hpp> +#include <FiniteVolumesEulerUnknowns.hpp> + +template<typename MeshData> +class AcousticSolverWithMesh +{ + typedef typename MeshData::MeshType MeshType; + typedef FiniteVolumesEulerUnknowns<MeshData> UnknownsType; + + MeshData& m_mesh_data; + const MeshType& m_mesh; + const typename MeshType::Connectivity& m_connectivity; + + constexpr static size_t dimension = MeshType::dimension; + + typedef TinyVector<dimension> Rd; + typedef TinyMatrix<dimension> Rdd; + +private: + struct ReduceMin + { + private: + const Kokkos::View<const double*> x_; + + public: + typedef Kokkos::View<const double*>::non_const_value_type value_type; + + ReduceMin(const Kokkos::View<const double*>& x) : x_ (x) {} + + typedef Kokkos::View<const double*>::size_type size_type; + + KOKKOS_INLINE_FUNCTION void + operator() (const size_type i, value_type& update) const + { + if (x_(i) < update) { + update = x_(i); + } + } + + KOKKOS_INLINE_FUNCTION void + join (volatile value_type& dst, + const volatile value_type& src) const + { + if (src < dst) { + dst = src; + } + } + + KOKKOS_INLINE_FUNCTION void + init (value_type& dst) const + { // The identity under max is -Inf. + dst= Kokkos::reduction_identity<value_type>::min(); + } + }; + + + KOKKOS_INLINE_FUNCTION + const Kokkos::View<const double*> + computeRhoCj(const Kokkos::View<const double*>& rhoj, + const Kokkos::View<const double*>& cj) + { + Kokkos::parallel_for(m_mesh.numberOfCells(), KOKKOS_LAMBDA(const int& j) { + m_rhocj[j] = rhoj[j]*cj[j]; + }); + return m_rhocj; + } + + KOKKOS_INLINE_FUNCTION + const Kokkos::View<const Rdd**> + computeAjr(const Kokkos::View<const double*>& rhocj, + const Kokkos::View<const Rd**>& Cjr) { + const Kokkos::View<const unsigned short*> cell_nb_nodes + = m_connectivity.cellNbNodes(); + + Kokkos::parallel_for(m_mesh.numberOfCells(), KOKKOS_LAMBDA(const int& j) { + for (int r=0; r<cell_nb_nodes[j]; ++r) { + m_Ajr(j,r) = tensorProduct(rhocj(j)*Cjr(j,r), Cjr(j,r)); + } + }); + + return m_Ajr; + } + + KOKKOS_INLINE_FUNCTION + const Kokkos::View<const Rdd*> + computeAr(const Kokkos::View<const Rdd**>& Ajr) { + const Kokkos::View<const unsigned int**> node_cells = m_connectivity.nodeCells(); + const Kokkos::View<const unsigned short**> node_cell_local_node = m_connectivity.nodeCellLocalNode(); + const Kokkos::View<const unsigned short*> node_nb_cells = m_connectivity.nodeNbCells(); + + Kokkos::parallel_for(m_mesh.numberOfNodes(), KOKKOS_LAMBDA(const int& r) { + Rdd sum = zero; + for (int j=0; j<node_nb_cells(r); ++j) { + const int J = node_cells(r,j); + const int R = node_cell_local_node(r,j); + sum += Ajr(J,R); + } + m_Ar(r) = sum; + }); + + return m_Ar; + } + + KOKKOS_INLINE_FUNCTION + const Kokkos::View<const Rd*> + computeBr(const Kokkos::View<const Rdd**>& Ajr, + const Kokkos::View<const Rd**>& Cjr, + const Kokkos::View<const Rd*>& uj, + const Kokkos::View<const double*>& pj) { + const Kokkos::View<const unsigned int**>& node_cells = m_connectivity.nodeCells(); + const Kokkos::View<const unsigned short**>& node_cell_local_node = m_connectivity.nodeCellLocalNode(); + const Kokkos::View<const unsigned short*>& node_nb_cells = m_connectivity.nodeNbCells(); + + Kokkos::parallel_for(m_mesh.numberOfNodes(), KOKKOS_LAMBDA(const int& r) { + Rd& br = m_br(r); + br = zero; + for (int j=0; j<node_nb_cells(r); ++j) { + const int J = node_cells(r,j); + const int R = node_cell_local_node(r,j); + br += Ajr(J,R)*uj(J) + pj(J)*Cjr(J,R); + } + }); + + return m_br; + } + + Kokkos::View<Rd*> + computeUr(const Kokkos::View<const Rdd*>& Ar, + const Kokkos::View<const Rd*>& br) { + inverse(Ar, m_inv_Ar); + const Kokkos::View<const Rdd*> invAr = m_inv_Ar; + Kokkos::parallel_for(m_mesh.numberOfNodes(), KOKKOS_LAMBDA(const int& r) { + m_ur[r]=invAr(r)*br(r); + }); + m_ur[0]=zero; + m_ur[m_mesh.numberOfNodes()-1]=zero; + + return m_ur; + } + + Kokkos::View<Rd**> + computeFjr(const Kokkos::View<const Rdd**>& Ajr, + const Kokkos::View<const Rd*>& ur, + const Kokkos::View<const Rd**>& Cjr, + const Kokkos::View<const Rd*>& uj, + const Kokkos::View<const double*>& pj) { + const Kokkos::View<const unsigned int**>& cell_nodes = m_connectivity.cellNodes(); + const Kokkos::View<const unsigned short*> cell_nb_nodes + = m_connectivity.cellNbNodes(); + + Kokkos::parallel_for(m_mesh.numberOfCells(), KOKKOS_LAMBDA(const int& j) { + for (int r=0; r<cell_nb_nodes[j]; ++r) { + m_Fjr(j,r) = Ajr(j,r)*(uj(j)-ur(cell_nodes(j,r)))+pj(j)*Cjr(j,r); + } + }); + + return m_Fjr; + } + + void inverse(const Kokkos::View<const Rdd*>& A, + Kokkos::View<Rdd*>& inv_A) const { + Kokkos::parallel_for(A.size(), KOKKOS_LAMBDA(const int& r) { + inv_A(r) = Rdd{1./(A(r)(0,0))}; + }); + } + + void inverse(const Kokkos::View<const double*>& x, + Kokkos::View<double*>& inv_x) const { + Kokkos::parallel_for(x.size(), KOKKOS_LAMBDA(const int& r) { + inv_x(r) = 1./x(r); + }); + } + + KOKKOS_INLINE_FUNCTION + double acoustic_dt(const Kokkos::View<const double*>& Vj, + const Kokkos::View<const double*>& cj) const { + Kokkos::parallel_for(m_mesh.numberOfCells(), KOKKOS_LAMBDA(const int& j){ + m_Vj_over_cj[j] = Vj[j]/cj[j]; + }); + + double dt = std::numeric_limits<double>::max(); + Kokkos::parallel_reduce(m_mesh.numberOfCells(), ReduceMin(m_Vj_over_cj), dt); + + return dt; + } + + KOKKOS_INLINE_FUNCTION + void computeExplicitFluxes(const Kokkos::View<const Rd*>& xr, + const Kokkos::View<const Rd*>& xj, + const Kokkos::View<const double*>& rhoj, + const Kokkos::View<const Rd*>& uj, + const Kokkos::View<const double*>& pj, + const Kokkos::View<const double*>& cj, + const Kokkos::View<const double*>& Vj, + const Kokkos::View<const Rd**>& Cjr) { + const Kokkos::View<const double*> rhocj = computeRhoCj(rhoj, cj); + const Kokkos::View<const Rdd**> Ajr = computeAjr(rhocj, Cjr); + + const Kokkos::View<const Rdd*> Ar = computeAr(Ajr); + const Kokkos::View<const Rd*> br = computeBr(Ajr, Cjr, uj, pj); + + Kokkos::View<Rd*> ur = m_ur; + Kokkos::View<Rd**> Fjr = m_Fjr; + ur = computeUr(Ar, br); + Fjr = computeFjr(Ajr, ur, Cjr, uj, pj); + } + + Kokkos::View<Rd*> m_br; + Kokkos::View<Rdd**> m_Ajr; + Kokkos::View<Rdd*> m_Ar; + Kokkos::View<Rdd*> m_inv_Ar; + Kokkos::View<Rd**> m_Fjr; + Kokkos::View<Rd*> m_ur; + Kokkos::View<double*> m_rhocj; + Kokkos::View<double*> m_Vj_over_cj; + + +public: + AcousticSolverWithMesh(MeshData& mesh_data, + UnknownsType& unknowns) + : m_mesh_data(mesh_data), + m_mesh(mesh_data.mesh()), + m_connectivity(m_mesh.connectivity()), + m_br("br", m_mesh.numberOfNodes()), + m_Ajr("Ajr", m_mesh.numberOfCells(), m_connectivity.maxNbNodePerCell()), + m_Ar("Ar", m_mesh.numberOfNodes()), + m_inv_Ar("inv_Ar", m_mesh.numberOfNodes()), + m_Fjr("Fjr", m_mesh.numberOfCells(), m_connectivity.maxNbNodePerCell()), + m_ur("ur", m_mesh.numberOfNodes()), + m_rhocj("rho_c", m_mesh.numberOfCells()), + m_Vj_over_cj("Vj_over_cj", m_mesh.numberOfCells()) + { + Kokkos::View<double*> rhoj = unknowns.rhoj(); + Kokkos::View<Rd*> uj = unknowns.uj(); + Kokkos::View<double*> Ej = unknowns.Ej(); + + Kokkos::View<double*> ej = unknowns.ej(); + Kokkos::View<double*> pj = unknowns.pj(); + Kokkos::View<double*> gammaj = unknowns.gammaj(); + Kokkos::View<double*> cj = unknowns.cj(); + const Kokkos::View<const double*> mj = unknowns.mj(); + + Kokkos::View<Rd*> xr = m_mesh.xr(); + + const Kokkos::View<const Rd*> xj = m_mesh_data.xj(); + const Kokkos::View<const double*> Vj = m_mesh_data.Vj(); + + const Kokkos::View<const unsigned int**>& cell_nodes = m_connectivity.cellNodes(); + const Kokkos::View<const Rd**> Cjr = m_mesh_data.Cjr(); + + Kokkos::View<double*> inv_mj("inv_mj",m_mesh.numberOfCells()); + inverse(mj, inv_mj); + + const double tmax=0.2; + double t=0; + + int itermax=std::numeric_limits<int>::max(); + int iteration=0; + + BlockPerfectGas block_eos(rhoj, ej, pj, gammaj, cj); + + while((t<tmax) and (iteration<itermax)) { + double dt = 0.4*acoustic_dt(Vj, cj); + if (t+dt<tmax) { + t+=dt; + } else { + dt=tmax-t; + t=tmax; + } + + computeExplicitFluxes(xr, xj, rhoj, uj, pj, cj, Vj, Cjr); + + const Kokkos::View<const Rd**> Fjr = m_Fjr; + const Kokkos::View<const Rd*> ur = m_ur; + const Kokkos::View<const unsigned short*> cell_nb_nodes + = m_connectivity.cellNbNodes(); + + Kokkos::parallel_for(m_mesh.numberOfCells(), KOKKOS_LAMBDA(const int& j) { + Rd momentum_fluxes = zero; + double energy_fluxes = 0; + for (int R=0; R<cell_nb_nodes[j]; ++R) { + const int r=cell_nodes(j,R); + momentum_fluxes += Fjr(j,R); + energy_fluxes += (Fjr(j,R), ur[r]); + } + uj[j] -= (dt*inv_mj[j]) * momentum_fluxes; + Ej[j] -= (dt*inv_mj[j]) * energy_fluxes; + }); + + Kokkos::parallel_for(m_mesh.numberOfCells(), KOKKOS_LAMBDA(const int& j) { + ej[j] = Ej[j] - 0.5 * (uj[j],uj[j]); + }); + + Kokkos::parallel_for(m_mesh.numberOfNodes(), KOKKOS_LAMBDA(const int& r){ + xr[r] += dt*ur[r]; + }); + + m_mesh_data.updateAllData(); + + Kokkos::parallel_for(m_mesh.numberOfCells(), KOKKOS_LAMBDA(const int& j){ + rhoj[j] = mj[j]/Vj[j]; + }); + + block_eos.updatePandCFromRhoE(); + + ++iteration; + } + + std::cout << "* " << rang::style::underline << "Final time" << rang::style::reset + << ": " << rang::fgB::green << t << rang::fg::reset << " (" << iteration << " iterations)\n"; + + } +}; + +#endif // ACOUSTIC_SOLVER_WITH_MESH_HPP diff --git a/experimental/Connectivity1D.hpp b/experimental/Connectivity1D.hpp new file mode 100644 index 0000000000000000000000000000000000000000..6431faae7e538366ef3063058b1c6da849ec9fbc --- /dev/null +++ b/experimental/Connectivity1D.hpp @@ -0,0 +1,167 @@ +#ifndef CONNECTIVITY_1D_HPP +#define CONNECTIVITY_1D_HPP + +#include <Kokkos_Core.hpp> +#include <TinyVector.hpp> + +class Connectivity1D +{ +public: + static constexpr size_t dimension = 1; + +private: + const size_t m_number_of_cells; + const size_t& m_number_of_faces; + const size_t m_number_of_nodes; + + Kokkos::View<unsigned int*[2]> m_cell_nodes; + const Kokkos::View<unsigned int*[2]>& m_cell_faces; + + Kokkos::View<unsigned short*> m_cell_nb_nodes; + const Kokkos::View<unsigned short*> m_cell_nb_faces; + + Kokkos::View<unsigned short*> m_node_nb_cells; + const Kokkos::View<unsigned short*>& m_face_nb_cells; + + Kokkos::View<unsigned int*[2]> m_node_cells; + const Kokkos::View<unsigned int*[2]>& m_face_cells; + + Kokkos::View<unsigned short*[2]> m_node_cell_local_node; + const Kokkos::View<unsigned short*[2]>& m_face_cell_local_face; + + const size_t m_max_nb_node_per_cell; + +public: + const size_t& numberOfNodes() const + { + return m_number_of_nodes; + } + + const size_t& numberOfFaces() const + { + return m_number_of_faces; + } + + const size_t& numberOfCells() const + { + return m_number_of_cells; + } + + const size_t& maxNbNodePerCell() const + { + return m_max_nb_node_per_cell; + } + + const Kokkos::View<const unsigned int**> cellNodes() const + { + return m_cell_nodes; + } + + const Kokkos::View<const unsigned int**> cellFaces() const + { + return m_cell_faces; + } + + const Kokkos::View<const unsigned short*> nodeNbCells() const + { + return m_node_nb_cells; + } + + const Kokkos::View<const unsigned short*> cellNbNodes() const + { + return m_cell_nb_nodes; + } + + const Kokkos::View<const unsigned short*> cellNbFaces() const + { + return m_cell_nb_faces; + } + + const Kokkos::View<const unsigned short*> faceNbCells() const + { + return m_face_nb_cells; + } + + const Kokkos::View<const unsigned int**> nodeCells() const + { + return m_node_cells; + } + + const Kokkos::View<const unsigned int**> faceCells() const + { + return m_face_cells; + } + + const Kokkos::View<const unsigned short**> nodeCellLocalNode() const + { + return m_node_cell_local_node; + } + + const Kokkos::View<const unsigned short**> faceCellLocalFace() const + { + return m_face_cell_local_face; + } + + Connectivity1D(const Connectivity1D&) = delete; + + Connectivity1D(const size_t& number_of_cells) + : m_number_of_cells (number_of_cells), + m_number_of_faces (m_number_of_nodes), + m_number_of_nodes (number_of_cells+1), + m_cell_nodes ("cell_nodes", m_number_of_cells), + m_cell_faces (m_cell_nodes), + m_cell_nb_nodes ("cell_nb_nodes", m_number_of_cells), + m_cell_nb_faces (m_cell_nb_nodes), + m_node_nb_cells ("node_nb_cells",m_number_of_nodes), + m_face_nb_cells (m_node_nb_cells), + m_node_cells ("node_cells", m_number_of_nodes), + m_face_cells (m_node_cells), + m_node_cell_local_node ("node_cell_local_node", m_number_of_nodes), + m_face_cell_local_face (m_node_cell_local_node), + m_max_nb_node_per_cell (2) + { + assert(number_of_cells>0); + + Kokkos::parallel_for(m_number_of_cells, KOKKOS_LAMBDA(const size_t& j) { + m_cell_nb_nodes[j] = 2; + }); + + Kokkos::parallel_for(m_number_of_cells, KOKKOS_LAMBDA(const size_t& j) { + for (int R=0; R<2; ++R) { + m_cell_nodes(j,R)=j+R; + } + }); + + Kokkos::parallel_for(m_number_of_nodes, KOKKOS_LAMBDA(const size_t& r) { + m_node_nb_cells(r) = 2; + }); + m_node_nb_cells(0) = 1; + m_node_nb_cells(m_number_of_nodes-1) = 1; + + + m_node_cells(0,0) = 0; + Kokkos::parallel_for(m_number_of_nodes-2, KOKKOS_LAMBDA(const int& r) { + m_node_cells(r+1,0) = r; + m_node_cells(r+1,1) = r+1; + }); + m_node_cells(m_number_of_nodes-1,0) = m_number_of_cells-1; + + Kokkos::parallel_for(m_number_of_nodes, KOKKOS_LAMBDA(const int& r) { + for (int J=0; J<m_node_nb_cells(r); ++J) { + int j = m_node_cells(r,J); + for (int R=0; R<2; ++R) { + if (m_cell_nodes(j,R) == r) { + m_node_cell_local_node(r,J) = R; + } + } + } + }); + } + + ~Connectivity1D() + { + ; + } +}; + +#endif // CONNECTIVITY_1D_HPP diff --git a/experimental/FiniteVolumesEulerUnknowns.hpp b/experimental/FiniteVolumesEulerUnknowns.hpp new file mode 100644 index 0000000000000000000000000000000000000000..8f4ac4e572f487fa2c8937063fac949484bf2bf3 --- /dev/null +++ b/experimental/FiniteVolumesEulerUnknowns.hpp @@ -0,0 +1,168 @@ +#ifndef FINITE_VOLUMES_EULER_UNKNOWNS_HPP +#define FINITE_VOLUMES_EULER_UNKNOWNS_HPP + +template <typename TMeshData> +class FiniteVolumesEulerUnknowns +{ +public: + typedef TMeshData MeshDataType; + typedef typename MeshDataType::MeshType MeshType; + + static constexpr size_t dimension = MeshType::dimension; + typedef TinyVector<dimension> Rd; + +private: + const MeshDataType& m_mesh_data; + const MeshType& m_mesh; + + Kokkos::View<double*> m_rhoj; + Kokkos::View<Rd*> m_uj; + Kokkos::View<double*> m_Ej; + + Kokkos::View<double*> m_ej; + Kokkos::View<double*> m_pj; + Kokkos::View<double*> m_gammaj; + Kokkos::View<double*> m_cj; + Kokkos::View<double*> m_mj; + +public: + Kokkos::View<double*> rhoj() + { + return m_rhoj; + } + + const Kokkos::View<const double*> rhoj() const + { + return m_rhoj; + } + + Kokkos::View<Rd*> uj() + { + return m_uj; + } + + const Kokkos::View<const Rd*> uj() const + { + return m_uj; + } + + Kokkos::View<double*> Ej() + { + return m_Ej; + } + + const Kokkos::View<const double*> Ej() const + { + return m_Ej; + } + + Kokkos::View<double*> ej() + { + return m_ej; + } + + const Kokkos::View<const double*> ej() const + { + return m_ej; + } + + Kokkos::View<double*> pj() + { + return m_pj; + } + + const Kokkos::View<const double*> pj() const + { + return m_pj; + } + + Kokkos::View<double*> gammaj() + { + return m_gammaj; + } + + const Kokkos::View<const double*> gammaj() const + { + return m_gammaj; + } + + Kokkos::View<double*> cj() + { + return m_cj; + } + + const Kokkos::View<const double*> cj() const + { + return m_cj; + } + + Kokkos::View<double*> mj() + { + return m_mj; + } + + const Kokkos::View<const double*> mj() const + { + return m_mj; + } + + void initializeSod() + { + const Kokkos::View<const Rd*> xj = m_mesh_data.xj(); + + Kokkos::parallel_for(m_mesh.numberOfCells(), KOKKOS_LAMBDA(const int& j){ + if (xj[j][0]<0.5) { + m_rhoj[j]=1; + } else { + m_rhoj[j]=0.125; + } + }); + + Kokkos::parallel_for(m_mesh.numberOfCells(), KOKKOS_LAMBDA(const int& j){ + if (xj[j][0]<0.5) { + m_pj[j]=1; + } else { + m_pj[j]=0.1; + } + }); + + Kokkos::parallel_for(m_mesh.numberOfCells(), KOKKOS_LAMBDA(const int& j){ + m_uj[j] = zero; + }); + + Kokkos::parallel_for(m_mesh.numberOfCells(), KOKKOS_LAMBDA(const int& j){ + m_gammaj[j] = 1.4; + }); + + BlockPerfectGas block_eos(m_rhoj, m_ej, m_pj, m_gammaj, m_cj); + block_eos.updateEandCFromRhoP(); + + Kokkos::parallel_for(m_mesh.numberOfCells(), KOKKOS_LAMBDA(const int& j){ + m_Ej[j] = m_ej[j]+0.5*(m_uj[j],m_uj[j]); + }); + + const Kokkos::View<const double*> Vj = m_mesh_data.Vj(); + Kokkos::parallel_for(m_mesh.numberOfCells(), KOKKOS_LAMBDA(const int& j){ + m_mj[j] = m_rhoj[j] * Vj[j]; + }); + + } + + FiniteVolumesEulerUnknowns(const MeshDataType& mesh_data) + : m_mesh_data(mesh_data), + m_mesh(m_mesh_data.mesh()), + m_rhoj("rhoj",m_mesh.numberOfCells()), + m_uj("uj",m_mesh.numberOfCells()), + m_Ej("Ej",m_mesh.numberOfCells()), + m_ej("ej",m_mesh.numberOfCells()), + m_pj("pj",m_mesh.numberOfCells()), + m_gammaj("gammaj",m_mesh.numberOfCells()), + m_cj("cj",m_mesh.numberOfCells()), + m_mj("mj",m_mesh.numberOfCells()) + { + ; + } + +}; + +#endif // FINITE_VOLUMES_EULER_UNKNOWNS_HPP diff --git a/experimental/Mesh.hpp b/experimental/Mesh.hpp new file mode 100644 index 0000000000000000000000000000000000000000..f199266b5ce8efef07d946a40134690233ae8952 --- /dev/null +++ b/experimental/Mesh.hpp @@ -0,0 +1,63 @@ +#ifndef MESH_HPP +#define MESH_HPP + +#include <Kokkos_Core.hpp> +#include <TinyVector.hpp> + +template <typename ConnectivityType> +class Mesh +{ +public: + typedef ConnectivityType Connectivity; + static constexpr size_t dimension = ConnectivityType::dimension; + typedef TinyVector<dimension> Rd; + +private: + const Connectivity& m_connectivity; + Kokkos::View<Rd*> m_xr; + +public: + + const Connectivity& connectivity() const + { + return m_connectivity; + } + + const size_t& numberOfNodes() const + { + return m_connectivity.numberOfNodes(); + } + + const size_t& numberOfFaces() const + { + return m_connectivity.numberOfFaces(); + } + + const size_t& numberOfCells() const + { + return m_connectivity.numberOfCells(); + } + + #warning PASSER CES NOUVEAUX VECTEURS EN CONST + Kokkos::View<Rd*> xr() const + { + return m_xr; + } + + Mesh(const Connectivity& connectivity) + : m_connectivity(connectivity), + m_xr("xr", connectivity.numberOfNodes()) + { + const double delta_x = 1./connectivity.numberOfCells(); + Kokkos::parallel_for(connectivity.numberOfNodes(), KOKKOS_LAMBDA(const int& r){ + m_xr[r][0] = r*delta_x; + }); + } + + ~Mesh() + { + ; + } +}; + +#endif // MESH_HPP diff --git a/experimental/MeshData.hpp b/experimental/MeshData.hpp new file mode 100644 index 0000000000000000000000000000000000000000..c2125f5f0b04a47b7b982102949f81cf94b27d90 --- /dev/null +++ b/experimental/MeshData.hpp @@ -0,0 +1,138 @@ +#ifndef MESH_DATA_HPP +#define MESH_DATA_HPP + +#include <Kokkos_Core.hpp> +#include <TinyVector.hpp> + +template <typename M> +class MeshData +{ +public: + typedef M MeshType; + + typedef TinyVector<dimension> Rd; + typedef double R; + + static constexpr size_t dimension = MeshType::dimension; + static_assert(dimension>0, "dimension must be strictly positive"); + + static constexpr R inv_dimension = 1./dimension; + +private: + const MeshType& m_mesh; + Kokkos::View<Rd**> m_Cjr; + Kokkos::View<Rd*> m_xj; + Kokkos::View<R*> m_Vj; + + template<size_t Dim> + KOKKOS_INLINE_FUNCTION + void _updateCenter(); + + template <> + KOKKOS_INLINE_FUNCTION + void _updateCenter<1>() + { + const Kokkos::View<const Rd*> xr = m_mesh.xr(); + + const Kokkos::View<const unsigned int**>& cell_nodes + = m_mesh.connectivity().cellNodes(); + Kokkos::parallel_for(m_mesh.numberOfCells(), KOKKOS_LAMBDA(const int& j){ + m_xj[j] = 0.5*(xr[cell_nodes(j,0)]+xr[cell_nodes(j,1)]); + }); + } + + template<size_t Dim> + KOKKOS_INLINE_FUNCTION + void _updateVolume() + { + const Kokkos::View<const unsigned int**>& cell_nodes + = m_mesh.connectivity().cellNodes(); + + const Kokkos::View<const unsigned short*> cell_nb_nodes + = m_mesh.connectivity().cellNbNodes(); + + const Kokkos::View<const Rd*> xr = m_mesh.xr(); + + Kokkos::parallel_for(m_mesh.numberOfCells(), KOKKOS_LAMBDA(const int& j){ + R sum_cjr_xr = 0; + for (int R=0; R<cell_nb_nodes[j]; ++R) { + sum_cjr_xr += (xr[cell_nodes(j,R)], m_Cjr(j,R)); + } + m_Vj[j] = inv_dimension * sum_cjr_xr; + }); + } + + template<> + KOKKOS_INLINE_FUNCTION + void _updateVolume<1>() + { + const Kokkos::View<const Rd*> xr = m_mesh.xr(); + + const Kokkos::View<const unsigned int**>& cell_nodes + = m_mesh.connectivity().cellNodes(); + + Kokkos::parallel_for(m_mesh.numberOfCells(), KOKKOS_LAMBDA(const int& j){ + m_Vj[j] + = (xr[cell_nodes(j,1)], m_Cjr(j,1)) + + (xr[cell_nodes(j,0)], m_Cjr(j,0)); + }); + } + + template<size_t Dim> + KOKKOS_INLINE_FUNCTION + void _updateCjr(); + + template<> + KOKKOS_INLINE_FUNCTION + void _updateCjr<1>() {} + +public: + + const MeshType& mesh() const + { + return m_mesh; + } + + + const Kokkos::View<const Rd**> Cjr() const + { + return m_Cjr; + } + + const Kokkos::View<const Rd*> xj() const + { + return m_xj; + } + + const Kokkos::View<const R*> Vj() const + { + return m_Vj; + } + + void updateAllData() + { + this->_updateCenter<dimension>(); + this->_updateVolume<dimension>(); + this->_updateCjr<dimension>(); + } + + MeshData(const MeshType& mesh) + : m_mesh(mesh), + m_Cjr("Cjr", mesh.numberOfCells(), mesh.connectivity().maxNbNodePerCell()), + m_xj("xj", mesh.numberOfCells()), + m_Vj("Vj", mesh.numberOfCells()) + { + Kokkos::parallel_for(m_mesh.numberOfCells(), KOKKOS_LAMBDA(const int& j) { + m_Cjr(j,0)=-1; + m_Cjr(j,1)= 1; + }); + this->updateAllData(); + } + + ~MeshData() + { + ; + } +}; + +#endif // MESH_DATA_HPP diff --git a/main.cpp b/main.cpp index f2a8f173cdc0bb527e731c54cd5072a4abac99bb..56081e71993150e70fbfdba010e6b2907961a74d 100644 --- a/main.cpp +++ b/main.cpp @@ -11,7 +11,11 @@ #include <AcousticSolver.hpp> #include <AcousticSolverTest.hpp> +#include <Connectivity1D.hpp> +#include <AcousticSolverWithMesh.hpp> + #include <TinyVector.hpp> +#include <TinyMatrix.hpp> #include <CLI/CLI.hpp> #include <cassert> @@ -81,19 +85,26 @@ int main(int argc, char *argv[]) std::map<std::string, double> method_cost_map; - { // Basic function based acoustic solver - Kokkos::Timer timer; - timer.reset(); - RawKokkos::AcousticSolver(number); - method_cost_map["RawKokkos"] = timer.seconds(); - } - - { // class for acoustic solver (mesh less) - Kokkos::Timer timer; - timer.reset(); - MeshLessAcousticSolver acoustic_solver(number); - method_cost_map["MeshLessAcousticSolver"] = timer.seconds(); - } + // { // Basic function based acoustic solver + // Kokkos::Timer timer; + // timer.reset(); + // RawKokkos::AcousticSolver(number); + // method_cost_map["RawKokkos"] = timer.seconds(); + // } + + // { // class for acoustic solver (mesh less) + // Kokkos::Timer timer; + // timer.reset(); + // MeshLessAcousticSolver acoustic_solver(number); + // method_cost_map["MeshLessAcousticSolver"] = timer.seconds(); + // } + + // { // class for acoustic solver + // Kokkos::Timer timer; + // timer.reset(); + // AcousticSolver acoustic_solver(number); + // method_cost_map["AcousticSolver"] = timer.seconds(); + // } { // class for acoustic solver test Kokkos::Timer timer; @@ -102,18 +113,35 @@ int main(int argc, char *argv[]) method_cost_map["AcousticSolverTest"] = timer.seconds(); } - { // class for acoustic solver + + { // class for acoustic solver test Kokkos::Timer timer; timer.reset(); - AcousticSolver acoustic_solver(number); - method_cost_map["AcousticSolver"] = timer.seconds(); - } - - Kokkos::View<TinyVector<2,double>*[2]> test("test", 10); - constexpr size_t N = 3; - std::cout << "sizeof(TinyVector<" << N << ",double>)=" << sizeof(TinyVector<N,double>) << std::endl; - std::cout << "sizeof(double)=" << sizeof(double) << std::endl; + Connectivity1D connectivity(number); + typedef Mesh<Connectivity1D> MeshType; + typedef MeshData<MeshType> MeshDataType; + typedef FiniteVolumesEulerUnknowns<MeshDataType> UnknownsType; + + MeshType mesh(connectivity); + MeshDataType mesh_data(mesh); + UnknownsType unknowns(mesh_data); + + unknowns.initializeSod(); + + AcousticSolverWithMesh<MeshDataType> acoustic_solver(mesh_data, unknowns); + + method_cost_map["AcousticSolverWithMesh"] = timer.seconds(); + + { // gnuplot output for density + const Kokkos::View<const Rd*> xj = mesh_data.xj(); + const Kokkos::View<const double*> rhoj = unknowns.rhoj(); + std::ofstream fout("rho"); + for (int j=0; j<mesh.numberOfCells(); ++j) { + fout << xj[j][0] << ' ' << rhoj[j] << '\n'; + } + } + } Kokkos::finalize(); @@ -125,21 +153,16 @@ int main(int argc, char *argv[]) } for (const auto& method_cost : method_cost_map) { - std::cout << "* [" << std::setw(size) << std::left + std::cout << "* [" + << rang::fgB::cyan + << std::setw(size) << std::left << method_cost.first - << " ] Execution time: " + << rang::fg::reset + << "] Execution time: " << rang::style::bold << method_cost.second << rang::style::reset << '\n'; } - - TinyVector<2> x=1; - TinyVector<2> y=2; - - std::cout << x << "-" << y << "=" << x-y << std::endl; - std::cout << x << "+" << y << "=" << x+y << std::endl; - std::cout << "3*" << x << '=' << 3*x << std::endl; - x[1]=3; y[1]=0.1; - std::cout << "< " << x << " | " << y << " > = " << (x,y) << std::endl; + return 0; } diff --git a/packages/CLI11/.appveyor.yml b/packages/CLI11/.appveyor.yml index 6d4f5b5e63fed94324d5e54520e07a672ac2fc60..91a95a011ef5e8e38e5433e3464abb9720a7987f 100644 --- a/packages/CLI11/.appveyor.yml +++ b/packages/CLI11/.appveyor.yml @@ -1,6 +1,6 @@ branches: - except: - - gh-pages + only: + - master install: - set PATH=C:\Python36;%PATH% @@ -12,13 +12,14 @@ install: build_script: - mkdir build - cd build - - cmake .. -DCLI_SINGLE_FILE_TESTS=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_GENERATOR="Visual Studio 14 2015" - - cmake --build . + - ps: cmake .. -DCLI11_SINGLE_FILE_TESTS=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_GENERATOR="Visual Studio 14 2015" + - ps: cmake --build . - cd .. - conan create . CLIUtils/CLI11 test_script: - - ctest --output-on-failure -C Debug + - cd build + - ps: ctest --output-on-failure -C Debug notifications: - provider: Webhook diff --git a/packages/CLI11/.ci/build_cmake.sh b/packages/CLI11/.ci/build_cmake.sh deleted file mode 100644 index 5b2d69bb61fc49989eecb993da8ffc4c55acc4af..0000000000000000000000000000000000000000 --- a/packages/CLI11/.ci/build_cmake.sh +++ /dev/null @@ -1,17 +0,0 @@ -CMAKE_VERSION=3.9.6 -CMAKE_MVERSION=${CMAKE_VERSION%.*} -# Non Bash version: -# echo CMAKE_MVERSION=`$var | awk -F"." '{ print $1"."$2 }'` - -if [ "$TRAVIS_OS_NAME" = "linux" ] ; then CMAKE_URL="https://cmake.org/files/v${CMAKE_MVERSION}/cmake-${CMAKE_VERSION}-Linux-x86_64.tar.gz" ; fi -if [ "$TRAVIS_OS_NAME" = "osx" ] ; then CMAKE_URL="https://cmake.org/files/v$CMAKE_MVERSION/cmake-$CMAKE_VERSION-Darwin-x86_64.tar.gz" ; fi -cd "${DEPS_DIR}" - -if [[ ! -f "${DEPS_DIR}/cmake/bin/cmake" ]] ; then - echo "Downloading CMake $CMAKE_VERSION" - mkdir -p cmake - travis_retry wget --no-check-certificate --quiet -O - "${CMAKE_URL}" | tar --strip-components=1 -xz -C cmake -fi - -export PATH="${DEPS_DIR}/cmake/bin:${PATH}" -cd "${TRAVIS_BUILD_DIR}" diff --git a/packages/CLI11/.ci/build_doxygen.sh b/packages/CLI11/.ci/build_doxygen.sh index 5087f2be12e3d5c739ccec611fa59bac1de0732e..6772ecf97e98f4fbc4f84a51da18e8cc1d6cf724 100644 --- a/packages/CLI11/.ci/build_doxygen.sh +++ b/packages/CLI11/.ci/build_doxygen.sh @@ -1,3 +1,5 @@ +set -evx + DOXYGEN_URL="ftp://ftp.stack.nl/pub/users/dimitri/doxygen-1.8.13.src.tar.gz" cd "${DEPS_DIR}" @@ -15,3 +17,6 @@ fi export PATH="${DEPS_DIR}/doxygen/build/bin:${PATH}" cd "${TRAVIS_BUILD_DIR}" + +set +evx + diff --git a/packages/CLI11/.ci/build_lcov.sh b/packages/CLI11/.ci/build_lcov.sh index 3b89dd33c023ef192d82a326c522888a5a3acab0..8671d7881308cc3ac76908ff69764889c54430d8 100644 --- a/packages/CLI11/.ci/build_lcov.sh +++ b/packages/CLI11/.ci/build_lcov.sh @@ -1,3 +1,5 @@ +set -evx + LCOV_URL="http://ftp.de.debian.org/debian/pool/main/l/lcov/lcov_1.13.orig.tar.gz" cd "${DEPS_DIR}" @@ -9,3 +11,5 @@ fi export PATH="${DEPS_DIR}/lcov/bin:${PATH}" cd "${TRAVIS_BUILD_DIR}" + +set +evx diff --git a/packages/CLI11/.ci/check_tidy.sh b/packages/CLI11/.ci/check_tidy.sh index 55189d9e4624c340c15095cb5068047610b113b7..505b68eb9d47229b991e9c24a22d10b8ad6273c5 100755 --- a/packages/CLI11/.ci/check_tidy.sh +++ b/packages/CLI11/.ci/check_tidy.sh @@ -1,11 +1,21 @@ -#!/usr/bin/env sh +#!/usr/bin/env bash + +echo -en "travis_fold:start:script.build\\r" +echo "Building with tidy on..." set -evx -mkdir build-tidy || true +mkdir -p build-tidy cd build-tidy -CXX_FLAGS="-Werror -Wall -Wextra -pedantic -std=c++11" cmake .. -DCLANG_TIDY_FIX=ON +CXX_FLAGS="-Werror -Wcast-align -Wfloat-equal -Wimplicit-atomic-properties -Wmissing-declarations -Woverlength-strings -Wshadow -Wstrict-selector-match -Wundeclared-selector -Wunreachable-code -std=c++11" cmake .. -DCLANG_TIDY_FIX=ON cmake --build . +set -evx +echo -en "travis_fold:end:script.build\\r" +echo -en "travis_fold:start:script.compare\\r" +echo "Checking git diff..." +set -evx + git diff --exit-code --color set +evx +echo -en "travis_fold:end:script.compare\\r" diff --git a/packages/CLI11/.ci/make_and_test.sh b/packages/CLI11/.ci/make_and_test.sh new file mode 100755 index 0000000000000000000000000000000000000000..2ba81aeeffa627ae010c61162012a7af75d7998b --- /dev/null +++ b/packages/CLI11/.ci/make_and_test.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +echo -en "travis_fold:start:script.build\\r" +echo "Building..." +set -evx + +mkdir -p build +cd build +cmake .. -DCLI11_CXX_STD=$1 -DCLI11_SINGLE_FILE_TESTS=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER_LAUNCHER=ccache +cmake --build . -- -j2 + +set +evx +echo -en "travis_fold:end:script.build\\r" +echo -en "travis_fold:start:script.test\\r" +echo "Testing..." +set -evx + +ctest --output-on-failure + +set +evx +echo -en "travis_fold:end:script.test\\r" diff --git a/packages/CLI11/.ci/prepare_altern.sh b/packages/CLI11/.ci/prepare_altern.sh deleted file mode 100644 index e384b00347fff628d8ca88621d868d79d7fe02cf..0000000000000000000000000000000000000000 --- a/packages/CLI11/.ci/prepare_altern.sh +++ /dev/null @@ -1,18 +0,0 @@ -cd "${DEPS_DIR}" -mkdir -p extrabin -cd extrabin - -if [ "$CXX" = "g++" ] ; then - ln -s `which gcc-$COMPILER` gcc - ln -s `which g++-$COMPILER` g++ - ln -s `which gcov-$COMPILER` gcov -else - ln -s `which clang-$COMPILER` clang - ln -s `which clang++-$COMPILER` clang++ - ln -s `which clang-format-$COMPILER` clang-format - ln -s `which clang-tidy-$COMPILER` clang-tidy -fi - -export PATH="${DEPS_DIR}/extrabin":$PATH - -cd "${TRAVIS_BUILD_DIR}" diff --git a/packages/CLI11/.ci/run_codecov.sh b/packages/CLI11/.ci/run_codecov.sh index cd7219a8ceb31d8bca77e7ab8a797e45a5798cb0..28d149a530571c3eb8d58dec4c2ac61284e504a9 100755 --- a/packages/CLI11/.ci/run_codecov.sh +++ b/packages/CLI11/.ci/run_codecov.sh @@ -1,14 +1,27 @@ +#!/usr/bin/env bash + +echo -en "travis_fold:start:script.build\\r" +echo "Building..." set -evx cd ${TRAVIS_BUILD_DIR} mkdir -p build cd build -cmake .. -DCLI_SINGLE_FILE_TESTS=OFF -DCLI_EXAMPLES=OFF -DCMAKE_BUILD_TYPE=Coverage +cmake .. -DCLI11_SINGLE_FILE_TESTS=OFF -DCLI11_EXAMPLES=OFF -DCMAKE_BUILD_TYPE=Coverage cmake --build . -- -j2 -cmake --build . --target CLI_coverage +cmake --build . --target CLI11_coverage + +set +evx +echo -en "travis_fold:end:script.build\\r" +echo -en "travis_fold:start:script.lcov\\r" +echo "Capturing and uploading LCov..." +set -evx lcov --directory . --capture --output-file coverage.info # capture coverage info lcov --remove coverage.info '*/tests/*' '*/examples/*' '*gtest*' '*gmock*' '/usr/*' --output-file coverage.info # filter out system lcov --list coverage.info #debug info # Uploading report to CodeCov bash <(curl -s https://codecov.io/bash) || echo "Codecov did not collect coverage reports" + +set +evx +echo -en "travis_fold:end:script.lcov\\r" diff --git a/packages/CLI11/.ci/travis.sh b/packages/CLI11/.ci/travis.sh deleted file mode 100755 index 49cc2f8ea8e2abfdcd4b214e2ea1bc328e115273..0000000000000000000000000000000000000000 --- a/packages/CLI11/.ci/travis.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env sh -set -evx - -mkdir build || true -cd build -cmake .. -DCLI_CXX_STD=11 -DCLI_SINGLE_FILE_TESTS=ON -DCMAKE_BUILD_TYPE=Debug -cmake --build . -- -j2 -ctest --output-on-failure -if [ -n "$CLI_CXX_STD" ] && [ "$CLI_CXX_STD" -ge "14" ] ; then - cmake .. -DCLI_CXX_STD=14 -DCLI_SINGLE_FILE_TESTS=ON -DCMAKE_BUILD_TYPE=Debug - cmake --build . -- -j2 - ctest --output-on-failure -fi -if [ -n "$CLI_CXX_STD" ] && [ "$CLI_CXX_STD" -ge "17" ] ; then - cmake .. -DCLI_CXX_STD=17 -DCLI_SINGLE_FILE_TESTS=ON -DCMAKE_BUILD_TYPE=Debug - cmake --build . -- -j2 - ctest --output-on-failure -fi - -set +evx diff --git a/packages/CLI11/.github/CONTRIBUTING.md b/packages/CLI11/.github/CONTRIBUTING.md index 8db2a87a2294aabfb054ba8a6c53172d324b384f..c93c400b42c70fbda73022faad274e72e415b1cb 100644 --- a/packages/CLI11/.github/CONTRIBUTING.md +++ b/packages/CLI11/.github/CONTRIBUTING.md @@ -1,9 +1,41 @@ -# A few notes on contributions +Thanks for considering to write a Pull Request (PR) for CLI11! Here are a few guidelines to get you started: -If you want to add code, please make sure it passes the clang-format style (I am using LLVM 4.0): +Make sure you are comfortable with the license; all contributions are licensed under the original license. + +## Adding functionality +Make sure any new functions you add are are: + +* Documented by `///` documentation for Doxygen +* Mentioned in the instructions in the README, though brief mentions are okay +* Explained in your PR (or previously explained in an Issue mentioned in the PR) +* Completely covered by tests + +In general, make sure the addition is well thought out and does not increase the complexity of CLI11 needlessly. + +## Things you should know: + +* Once you make the PR, tests will run to make sure your code works on all supported platforms +* The test coverage is also measured, and that should remain 100% +* Formatting should be done with clang-format, otherwise the format check will not pass. However, it is trivial to apply this to your PR, so don't worry about this check. If you do have clang-format, just run `scripts/check_style.sh` +* Everything must pass clang-tidy as well, run with `-DCLANG_TIDY_FIX-ON` (make sure you use a single threaded build process!) + +Note that the style check is really just: ```bash git ls-files -- '.cpp' '.hpp' | xargs clang-format -i -style=file ``` -It is also a good idea to check this with `clang-tidy`; automatic fixes can be made using `-DCLANG_TIDY_FIX-ON` (resets to `OFF` when rerunning CMake). +And, if you want to always use it, feel free to install the git hook provided in scripts. + +## For developers releasing to Conan.io + +The steps to make a Conan.io release are: + +```bash +conan delete '*' # optional, I like to be clean + +conan create . cliutils/stable +conan upload "*" -r cli11 --all +``` + +Here I've assumed that the remote is `cli11`. diff --git a/packages/CLI11/.gitrepo b/packages/CLI11/.gitrepo index 11c78bd365d1a7fbb3db8acc8765f341c4d4a563..19886d90e79da7583fb45f6afb638364f3ed406c 100644 --- a/packages/CLI11/.gitrepo +++ b/packages/CLI11/.gitrepo @@ -6,6 +6,6 @@ [subrepo] remote = git@github.com:CLIUtils/CLI11.git branch = master - commit = b4b7d991f69add2e4fb007d1df4c14a088127d9b - parent = 8546076c470f8af8014418e169819302cd7d1f66 + commit = df5b157d97530ed605532e7ed0799fdf6659bbc3 + parent = 3c3894389721d339e70c9996ab528e8795d3f723 cmdver = 0.3.1 diff --git a/packages/CLI11/.travis.yml b/packages/CLI11/.travis.yml index c02c44b065513bf4b5b9ff235f2870b1f7e938d8..ac8ba76192e513b29dc6df5e0e4fe7567f4d86a0 100644 --- a/packages/CLI11/.travis.yml +++ b/packages/CLI11/.travis.yml @@ -1,82 +1,100 @@ language: cpp sudo: false dist: trusty + +# Exclude ghpages, +# but even better, don't build branch and PR, just PR +# Include tags starting with v and a digit branches: - exclude: - - gh-pages + only: + - master + - /^v\d/ + cache: + ccache: true + apt: true directories: - - "${TRAVIS_BUILD_DIR}/deps/cmake" - "${TRAVIS_BUILD_DIR}/deps/doxygen" + matrix: include: + # Default clang - compiler: clang - addons: - apt: - sources: - - ubuntu-toolchain-r-test - - llvm-toolchain-trusty-5.0 - packages: - - clang++-5.0 - env: - - COMPILER=5.0 - - CLI_CXX_STD=14 - - compiler: clang - addons: - apt: - packages: - - clang-3.9 - - clang-format-3.9 - - clang-tidy-3.9 + script: + - .ci/make_and_test.sh 11 + - .ci/make_and_test.sh 14 + - .ci/make_and_test.sh 17 + + # Check style/tidy + - compiler: clang env: - - COMPILER=3.9 - - CLI_CXX_STD=14 + - CHECK_STYLE=yes script: - cd "${TRAVIS_BUILD_DIR}" - scripts/check_style.sh - - ".ci/check_tidy.sh" + - .ci/check_tidy.sh + + # Docs and clang 3.5 - compiler: clang + env: + - DEPLOY_MAT=yes addons: apt: packages: - clang-3.5 - env: - - COMPILER=3.5 - - DEPLOY_MAT=yes - - DOXYFILE=$TRAVIS_BUILD_DIR/docs/Doxyfile + install: + - export CC=clang-3.5 + - export CXX=clang++-3.5 + script: + - .ci/make_and_test.sh 11 after_success: + - export DOXYFILE=$TRAVIS_BUILD_DIR/docs/Doxyfile - | if [ "${TRAVIS_BRANCH}" == "master" ] && [ "${TRAVIS_PULL_REQUEST}" == "false" ] then - echo "Updating docs" && cd $TRAVIS_BUILD_DIR && .ci/build_docs.sh + . .ci/build_doxygen.sh + .ci/build_docs.sh fi + + # GCC 6 and Coverage - compiler: gcc + env: + - GCC_VER=7 addons: apt: sources: - ubuntu-toolchain-r-test packages: - - g++-6 + - g++-7 - curl - lcov - env: - - COMPILER=6 - - CLI_CXX_STD=14 - before_install: + install: + - export CC=gcc-7 + - export CXX=g++-7 - DEPS_DIR="${TRAVIS_BUILD_DIR}/deps" - cd $TRAVIS_BUILD_DIR - ". .ci/build_lcov.sh" - ".ci/run_codecov.sh" + script: + - .ci/make_and_test.sh 11 + - .ci/make_and_test.sh 14 + - .ci/make_and_test.sh 17 + + # GCC 4.7 and Conan - compiler: gcc + env: + - GCC_VER=4.7 addons: apt: packages: - g++-4.7 - env: - - COMPILER=4.7 - before_install: + install: + - export CC=gcc-4.7 + - export CXX=g++-4.7 - python -m pip install --user conan - conan user + script: + - .ci/make_and_test.sh 11 after_success: - conan create . CLIUtils/stable - | @@ -86,33 +104,28 @@ matrix: conan user -p ${BINFROG_API_KEY} -r origin henryiii conan upload "*" -c -r origin --all fi + + # macOS and clang - os: osx compiler: clang - before_install: + install: - brew update - echo 'brew "python"' > Brewfile - echo 'brew "conan"' >> Brewfile + - echo 'brew "ccache"' >> Brewfile - brew bundle - python -m ensurepip --user - conan user after_success: - conan create . CLIUtils/CLI11 -install: -- python -c 'import sys; print(sys.version_info[:])' -- DEPS_DIR="${TRAVIS_BUILD_DIR}/deps" -- if [ "$TRAVIS_OS_NAME" = "linux" ]; then cd $TRAVIS_BUILD_DIR && . .ci/prepare_altern.sh - ; fi -- if [ "$TRAVIS_OS_NAME" = "linux" ] ; then cd $TRAVIS_BUILD_DIR && . .ci/build_cmake.sh - ; fi -- if [ "$TRAVIS_OS_NAME" = "linux" ] ; then cd $TRAVIS_BUILD_DIR && . .ci/build_doxygen.sh - ; fi -- cd "${DEPS_DIR}" -- if [ "$(python -c 'import sys; print(sys.version_info[0])')" = "2" ] ; then python - -m pip install --user pathlib ; fi -- cmake --version + +install: skip + script: -- cd "${TRAVIS_BUILD_DIR}" -- ".ci/travis.sh" +- .ci/make_and_test.sh 11 +- .ci/make_and_test.sh 14 + + deploy: provider: releases api_key: @@ -134,3 +147,4 @@ env: global: - secure: cY0OI609iTAxLRYuYQnNMi+H6n0dBwioTAoFXGGRTnngw2V9om3UmY5eUu4HQEQsQZovHdYpNhlSgRmdwQ4UqSp3FGyrwobf0kzacV4bVnMDeXDmHt8RzE5wP/LwDd8elNF6RRYjElY99f0k0FyXVd0fIvuVkGKQECNLOtEk0jQo+4YTh7dhuCxRhBYgTbNiRL6UJynfrcK0YN+DQ+8CJNupu2VxgaEpCSngTfvDHLcddcrXwpvn3MPc3FsDUbtN389ZCIe41qqIL0ATv46DQaTw4FOevyVfRyrBOznONoGCVeAYKL6VBdrk01Fh6aytF5zgI3hKaKobgEn+QFfzR6l68c6APvqA0Qv39iLjuh6KbdIV2YsqXfyt6FBgqP2xZuNEZW1jZ8LxUOLl2I40UEh87nFutvnSbfIzN+FcLrajm2H2jV2kZGNKAMx+4qxkZuXSre4JPkENfJm2WNFAKlqPt4ZSEQarkDYzZPcEr2I9fbGjQYVJICoN4LikCv9K5z7ujpTxCTNbVpQWZcEOT6QQBc6Vml/N/NKAIl9o2OeTLiXCmT31+KQMeO492KYNQ6VmkeqrVhGExOUcJdNyDJV9C+3mSekb3Sq78SneYRKDechkWbMl0ol07wGTdBwQQwgaorjRyn07x1rDxpPr3z19/+eubnpPUW4UQ5MYsjs= - secure: G6H5HA9pPUgsd96A+uvTxbLjR1rcT9NtxsknIkFDfzGDpffn6wVX+kCIQLf9zFDnQnsfYA/4piiuoBN5U5C7HQrh9UCvBVptXjWviea0Y7CRbMJZpw2rPvXWQtrFNzYkaV7kdJ5B0Mmvh6rcH/I8gKFrkdjF7i7sfzWdFWRU5QXfxXOk2n+xCXX6uFemxHH9850XEjVtnU7YYUebQFaoTYLLy05nlt9JaEF84wfJljY/SJX7I9gpNLtizE9MpJylnrwUeL66OqFievmjL3/bWpPUBjUF0WdtXYlVDja7O582FQDs94ofgqeGieGIMQ0VuovpbQOJSdjs5XHZwu2ce6HZxtOhJJqw6xEwbq43ZdofAlJ5GUEOgrr+j25zIDkdzOhliDKJtw5ysmmTUKEcZ36iWbCE0YP/IC42yOV9oOP6UkgbuwpVDdxAFRgLZLahW9Ok+c1PlzIauPxv+jIEI4rSEEJRKZG2JK3TXUdhd58mHBfQMNjKQMF+Y2wCCGjfMO0q4SgvBhYyb4oBTxEqnc2Pzh2DJdNzRFsV7ktsQSRglHGVI+1XTmQ+2kbBzNOQBLjOuRvDZENUhyxPKGZDHyAOMlVvYm8vvWebM1/F3YgDb/tPh33+EGSvpKkCZ5nUxB5e605H6gdYlNKNhuWKlEKTo2/kF0D39gAUCIcGbzw= + - CCACHE_CPP2: yes diff --git a/packages/CLI11/CHANGELOG.md b/packages/CLI11/CHANGELOG.md index 279ebe327c47642b3f895c2570b3c74cb7f742f2..0262b0e0cdde94a1a293b0f61490bfe4552c1dae 100644 --- a/packages/CLI11/CHANGELOG.md +++ b/packages/CLI11/CHANGELOG.md @@ -1,3 +1,44 @@ +## In progress + +* Make unlimited positionals vs. unlimited options more intuitive [#102] + +[#102]: https://github.com/CLIUtils/CLI11/issues/102 + + +## Version 1.5: Optionals + +This version introduced support for optionals, along with clarification and examples of custom conversion overloads. Enums now have been dropped from the automatic conversion system, allowing explicit protection for out-of-range ints (or a completely custom conversion). This version has some internal cleanup and improved support for the newest compilers. Several bugs were fixed, as well. + +Note: This is the final release with `requires`, please switch to `needs`. + +* Fix unlimited short options eating two values before checking for positionals when no space present [#90] +* Symmetric exclude text when excluding options, exclude can be called multiple times [#64] +* Support for `std::optional`, `std::experimental::optional`, and `boost::optional` added if `__has_include` is supported [#95] +* All macros/CMake variables now start with `CLI11_` instead of just `CLI_` [#95] +* The internal stream was not being cleared before use in some cases. Fixed. [#95] +* Using an emum now requires explicit conversion overload [#97] +* The separator `--` now is removed when it ends unlimited arguments [#100] + +Other, non-user facing changes: + +* Added `Macros.hpp` with better C++ mode discovery [#95] +* Deprecated macros added for all platforms +* C++17 is now tested on supported platforms [#95] +* Informational printout now added to CTest [#95] +* Better single file generation [#95] +* Added support for GTest on MSVC 2017 (but not in C++17 mode, will need next version of GTest) +* Types now have a specific size, separate from the expected number - cleaner and more powerful internally [#92] +* Examples now run as part of testing [#99] + +[#64]: https://github.com/CLIUtils/CLI11/issues/64 +[#90]: https://github.com/CLIUtils/CLI11/issues/90 +[#92]: https://github.com/CLIUtils/CLI11/issues/92 +[#95]: https://github.com/CLIUtils/CLI11/pull/95 +[#97]: https://github.com/CLIUtils/CLI11/pull/97 +[#99]: https://github.com/CLIUtils/CLI11/pull/99 +[#100]: https://github.com/CLIUtils/CLI11/pull/100 + + ## Version 1.4: More feedback This version adds lots of smaller fixes and additions after the refactor in version 1.3. More ways to download and use CLI11 in CMake have been added. INI files have improved support. diff --git a/packages/CLI11/CMakeLists.txt b/packages/CLI11/CMakeLists.txt index f31547e3b96c63be237bd20fb5089125fad2c81c..040ae0e0033878a5e0d0b4575f5d74f9faf3b14d 100644 --- a/packages/CLI11/CMakeLists.txt +++ b/packages/CLI11/CMakeLists.txt @@ -16,10 +16,10 @@ set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH}) # Only if built as the main project if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) # User settable - set(CLI_CXX_STD "11" CACHE STRING "The CMake standard to require") + set(CLI11_CXX_STD "11" CACHE STRING "The CMake standard to require") set(CUR_PROJ ON) - set(CMAKE_CXX_STANDARD ${CLI_CXX_STD}) + set(CMAKE_CXX_STANDARD ${CLI11_CXX_STD}) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -27,7 +27,7 @@ if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) if(MSVC) add_definitions("/W4") else() - add_definitions("-Wall -Wextra -pedantic") + add_definitions(-Wall -Wextra -pedantic -Wno-deprecated-declarations) endif() if(CMAKE_VERSION VERSION_GREATER 3.6) @@ -51,15 +51,18 @@ else() set(CUR_PROJ OFF) endif() +# Allow dependent options +include(CMakeDependentOption) + # Allow IDE's to group targets into folders set_property(GLOBAL PROPERTY USE_FOLDERS ON) if(CMAKE_BUILD_TYPE STREQUAL Coverage) include(CodeCoverage) - setup_target_for_coverage(CLI_coverage ctest coverage) + setup_target_for_coverage(CLI11_coverage ctest coverage) endif() -file(GLOB CLI_headers "${CMAKE_CURRENT_SOURCE_DIR}/include/CLI/*") +file(GLOB CLI11_headers "${CMAKE_CURRENT_SOURCE_DIR}/include/CLI/*") # To see in IDE, must be listed for target add_library(CLI11 INTERFACE) @@ -112,19 +115,14 @@ export(PACKAGE CLI11) # Single file test find_package(PythonInterp) -if(CUR_PROJ AND PYTHONINTERP_FOUND) - set(CLI_SINGLE_FILE_DEFAULT ON) -else() - set(CLI_SINGLE_FILE_DEFAULT OFF) -endif() +cmake_dependent_option(CLI11_SINGLE_FILE "Generate a single header file" ON "CUR_PROJ;PYTHONINTERP_FOUND" OFF) -option(CLI_SINGLE_FILE "Generate a single header file" ${CLI_SINGLE_FILE_DEFAULT}) -if(CLI_SINGLE_FILE) +if(CLI11_SINGLE_FILE) find_package(PythonInterp REQUIRED) file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/include") add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/include/CLI11.hpp" COMMAND "${PYTHON_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/scripts/MakeSingleHeader.py" "${CMAKE_CURRENT_BINARY_DIR}/include/CLI11.hpp" - DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/include/CLI/CLI.hpp" ${CLI_headers} + DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/include/CLI/CLI.hpp" ${CLI11_headers} ) add_custom_target(generate_cli_single_file ALL DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/include/CLI11.hpp") @@ -134,27 +132,23 @@ if(CLI_SINGLE_FILE) add_library(CLI11_SINGLE INTERFACE) target_link_libraries(CLI11_SINGLE INTERFACE CLI11) add_dependencies(CLI11_SINGLE generate_cli_single_file) - target_compile_definitions(CLI11_SINGLE INTERFACE -DCLI_SINGLE_FILE) + target_compile_definitions(CLI11_SINGLE INTERFACE -DCLI11_SINGLE_FILE) target_include_directories(CLI11_SINGLE INTERFACE "${CMAKE_CURRENT_BINARY_DIR}/include/") endif() -option(CLI_SINGLE_FILE_TESTS "Duplicate all the tests for a single file build" OFF) +option(CLI11_SINGLE_FILE_TESTS "Duplicate all the tests for a single file build" OFF) -option(CLI_TESTING "Build the tests and add them" ${CUR_PROJ}) -if(CLI_TESTING) +cmake_dependent_option(CLI11_TESTING "Build the tests and add them" ON "CUR_PROJ" OFF) +if(CLI11_TESTING) enable_testing() add_subdirectory(tests) endif() -option(CLI_EXAMPLES "Build the examples" ${CUR_PROJ}) -if(CLI_EXAMPLES) +cmake_dependent_option(CLI11_EXAMPLES "Build the examples" ON "CUR_PROJ" OFF) +if(CLI11_EXAMPLES) add_subdirectory(examples) endif() -if(NOT CUR_PROJ) - mark_as_advanced(CLI_SINGLE_FILE_TESTS CLI_EXAMPLES CLI_TESTING) -endif() - # Packaging support set(CPACK_PACKAGE_VENDOR "github.com/CLIUtils/CLI11") set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Command line interface") diff --git a/packages/CLI11/CONTRIBUTING.md b/packages/CLI11/CONTRIBUTING.md deleted file mode 100644 index f4a67842a0350a8f43366649ee1dbc434268d094..0000000000000000000000000000000000000000 --- a/packages/CLI11/CONTRIBUTING.md +++ /dev/null @@ -1,20 +0,0 @@ - -Thanks for considering to write a Pull Request (PR) for CLI11! Here are a few guidelines to get you started: - -Make sure you are comfortable with the license; all contributions are licensed under the original license. - -## Adding functionality -Make sure any new functions you add are are: - -* Documented by `///` documentation for Doxygen -* Mentioned in the instructions in the README, though brief mentions are okay -* Explained in your PR (or previously explained in an Issue mentioned in the PR) -* Completely covered by tests - -In general, make sure the addition is well thought out and does not increase the complexity of CLI11 if possible. - -## Things you should know: - -* Once you make the PR, tests will run to make sure your code works on all supported platforms -* The test coverage is also measured, and that should remain 100% -* Formatting should be done with clang-format, otherwise the format check will not pass. However, it is trivial to apply this to your PR, so don't worry about this check. If you do have clang-format, just run `scripts/check_style.sh` diff --git a/packages/CLI11/README.md b/packages/CLI11/README.md index f2c2cf0a11a0678a7934d0b8e6dc09ec9990d72e..4fd12b517afa27e95fcaf8b6a62c5efe81237ded 100644 --- a/packages/CLI11/README.md +++ b/packages/CLI11/README.md @@ -10,7 +10,7 @@ [Documentation][GitBook] • [API Reference][api-docs] • [What's new](./CHANGELOG.md) • -[Try CLI11 1.4 online][wandbox-link] +[Try CLI11 1.5 online][wandbox-link] # CLI11: Command line parser for C++11 @@ -63,7 +63,7 @@ After I wrote this, I also found the following libraries: | [Argh!] | Very minimalistic C++11 parser, single header. Don't have many features. No help generation?!?! At least it's exception-free.| | [CLI] | Custom language and parser. Huge build-system overkill for very little benefit. Last release in 2009, but still occasionally active. | -See [Awesome C++] for a less-biased list of parsers. +See [Awesome C++] for a less-biased list of parsers. You can also find other single file libraries at [Single file libs]. </p></details> <br/> @@ -87,7 +87,9 @@ There are some other possible "features" that are intentionally not supported by To use, there are two methods: 1. Copy `CLI11.hpp` from the [most recent release][Github Releases] into your include directory, and you are set. This is combined from the source files for every release. This includes the entire command parser library, but does not include separate utilities (like `Timer`, `AutoTimer`). The utilities are completely self contained and can be copied separately. -2. Use `CLI/*.hpp` files. You could check out the repository as a submodule, for example. You can use the `CLI11::CLI11` interface target when linking from `add_subdirectory`. You can also configure and optionally install the project, and then use `find_package(CLI11 CONFIG)` to get the `CLI11::CLI11` target. You can also use [Conan.io][conan-link]. (These are just conveniences to allow you to use your favorite method of managing packages; it's just header only so including the correct path and +2. Use `CLI/*.hpp` files. You could check out the repository as a submodule, for example. You can use the `CLI11::CLI11` interface target when linking from `add_subdirectory`. + You can also configure and optionally install the project, and then use `find_package(CLI11 CONFIG)` to get the `CLI11::CLI11` target. You can also use [Conan.io][conan-link]. + (These are just conveniences to allow you to use your favorite method of managing packages; it's just header only so including the correct path and using C++11 is all you really need.) To build the tests, checkout the repository and use CMake: @@ -164,6 +166,8 @@ An option name must start with a alphabetic character or underscore. For long op On a C++14 compiler, you can pass a callback function directly to `.add_flag`, while in C++11 mode you'll need to use `.add_flag_function` if you want a callback function. The function will be given the number of times the flag was passed. You can throw a relevant `CLI::ParseError` to signal a failure. +On a compiler that supports C++17's `__has_include`, you can also use `std::optional`, `std::experimental::optional`, and `boost::optional` directly in an `add_option` call. If you don't have `__has_include`, you can define `CLI11_BOOST_OPTIONAL` before including CLI11 to manually add support for `boost::optional`. See [CLI11 Internals] for information on how this was done and how you can add your own converters. + ### Example * `"one,-o,--one"`: Valid as long as not a flag, would create an option that can be specified positionally, or with `-o` or `--one` @@ -174,7 +178,7 @@ On a C++14 compiler, you can pass a callback function directly to `.add_flag`, w The add commands return a pointer to an internally stored `Option`. If you set the final argument to true, the default value is captured and printed on the command line with the help flag. This option can be used directly to check for the count (`->count()`) after parsing to avoid a string based lookup. Before parsing, you can set the following options: * `->required()`: The program will quit if this option is not present. This is `mandatory` in Plumbum, but required options seems to be a more standard term. For compatibility, `->mandatory()` also works. -* `->expected(N)`: Take `N` values instead of as many as possible, only for vector args. If negative, require at least `-N`. +* `->expected(N)`: Take `N` values instead of as many as possible, only for vector args. If negative, require at least `-N`; end with `--` or another recognized option. * `->needs(opt)`: This option requires another option to also be present, opt is an `Option` pointer. * `->excludes(opt)`: This option cannot be given with `opt` present, opt is an `Option` pointer. * `->envname(name)`: Gets the value from the environment if present and not passed on the command line. @@ -207,7 +211,7 @@ Extra positional arguments will cause the program to exit, so at least one posit If you set `.allow_extras()` on the main `App`, you will not get an error. You can access the missing options using `remaining` (if you have subcommands, `app.remaining(true)` will get all remaining options, subcommands included). You can access a vector of pointers to the parsed options in the original order using `parse_order()`. -If `--` is present in the command line, +If `--` is present in the command line that does not end an unlimited option, then everything after that is positional only. @@ -239,7 +243,7 @@ There are several options that are supported on the main app and subcommands. Th * `.add_subcommand(name, description="")` Add a subcommand, returns a pointer to the internally stored subcommand. * `.got_subcommand(App_or_name)`: Check to see if a subcommand was received on the command line * `.get_subcommands()`: The list of subcommands given on the command line -* `.get_parent()`: Get the parent App or nullptr if called on master App **Coming in version 1.4** +* `.get_parent()`: Get the parent App or nullptr if called on master App * `.parsed()`: True if this subcommand was given on the command line * `.set_callback(void() function)`: Set the callback that runs at the end of parsing. The options have already run at this point. * `.allow_extras()`: Do not throw an error if extra arguments are left over @@ -305,7 +309,11 @@ Also, in a related note, the `App` you get a pointer to is stored in the parent ## How it works Every `add_` option you have seen so far depends on one method that takes a lambda function. Each of these methods is just making a different lambda function with capture to populate the option. The function has full access to the vector of strings, so it knows how many times an option was passed or how many arguments it received (flags add empty strings to keep the counts correct). The lambda returns `true` if it could validate the option strings, and -`false` if it failed. If you wanted to extend this to support a new type, just use a lambda. An example of a new parser for `complex<double>` that supports all of the features of a standard `add_options` call is in [one of the tests](./tests/NewParseTest.cpp). A simpler example is shown below: +`false` if it failed. + +Other values can be added as long as they support `operator>>` (and defaults can be printed if they support `operator<<`). To add an enum, for example, provide a custom `operator>>` with an `istream` (inside the CLI namespace is fine if you don't want to interfere with an existing `operator>>`). + +If you wanted to extend this to support a completely new type, just use a lambda. An example of a new parser for `complex<double>` that supports all of the features of a standard `add_options` call is in [one of the tests](./tests/NewParseTest.cpp). A simpler example is shown below: ### Example @@ -382,7 +390,8 @@ Significant features and/or improvements to the code were contributed by: * [Lucas Czech](https://github.com/lczech) * [Mathias Soeken](https://github.com/msoeken) * [Nathan Hourt](https://github.com/nathanhourt) - +* [Stéphane Del Pino](https://github.com/delpinux) +* [Anton](https://github.com/SX91) CLI11 was developed at the [University of Cincinnati] to support of the [GooFit] library under [NSF Award 1414736]. Version 0.9 was featured in a [DIANA/HEP] meeting at CERN ([see the slides][DIANA slides]). Please give it a try! Feedback is always welcome. @@ -398,8 +407,8 @@ CLI11 was developed at the [University of Cincinnati] to support of the [GooFit] [gitter-badge]: https://badges.gitter.im/CLI11gitter/Lobby.svg [gitter]: https://gitter.im/CLI11gitter/Lobby [license-badge]: https://img.shields.io/badge/License-BSD-blue.svg -[conan-badge]: https://api.bintray.com/packages/cliutils/CLI11/CLI11/images/download.svg -[conan-link]: https://bintray.com/cliutils/CLI11/CLI11/_latestVersion +[conan-badge]: https://api.bintray.com/packages/cliutils/CLI11/CLI11%3Acliutils/images/download.svg +[conan-link]: https://bintray.com/cliutils/CLI11/CLI11%3Acliutils/_latestVersion [Github Releases]: https://github.com/CLIUtils/CLI11/releases [Github Issues]: https://github.com/CLIUtils/CLI11/issues [Github Pull Requests]: https://github.com/CLIUtils/CLI11/pulls @@ -421,6 +430,7 @@ CLI11 was developed at the [University of Cincinnati] to support of the [GooFit] [NSF Award 1414736]: https://nsf.gov/awardsearch/showAward?AWD_ID=1414736 [University of Cincinnati]: http://www.uc.edu [GitBook]: https://cliutils.gitlab.io/CLI11Tutorial +[CLI11 Internals]: https://cliutils.gitlab.io/CLI11Tutorial/chapters/internals.html [ProgramOptions.hxx]: https://github.com/Fytch/ProgramOptions.hxx [Argument Aggregator]: https://github.com/vietjtnguyen/argagg [Args]: https://github.com/Taywee/args @@ -431,8 +441,10 @@ CLI11 was developed at the [University of Cincinnati] to support of the [GooFit] [Version 1.0 post]: https://iscinumpy.gitlab.io/post/announcing-cli11-10/ [Version 1.3 post]: https://iscinumpy.gitlab.io/post/announcing-cli11-13/ [wandbox-online]: https://img.shields.io/badge/try%20it-online-orange.svg -[wandbox-link]: https://wandbox.org/permlink/g7tRkuU8xY3aTIVP +[wandbox-link]: https://wandbox.org/permlink/3a2C5qg6vUjSSpr7 [releases-badge]: https://img.shields.io/github/release/CLIUtils/CLI11.svg [cli11-po-compare]: https://iscinumpy.gitlab.io/post/comparing-cli11-and-boostpo/ -[DIANA slides]: https://indico.cern.ch/event/619465/contributions/2507949/attachments/1448567/2232649/20170424-diana-2.pdf -[Awesome C++]: https://github.com/fffaraz/awesome-cpp/blob/master/README.md#cli +[DIANA slides]: https://indico.cern.ch/event/619465/contributions/2507949/attachments/1448567/2232649/20170424-diana-2.pdf +[Awesome C++]: https://github.com/fffaraz/awesome-cpp/blob/master/README.md#cli +[CLI]: https://codesynthesis.com/projects/cli/ +[Single file libs]: https://github.com/nothings/single_file_libs/blob/master/README.md diff --git a/packages/CLI11/cmake/AddGoogletest.cmake b/packages/CLI11/cmake/AddGoogletest.cmake index 80eb9ed6e428ff6a41404f4265d52da89ccfe262..e6bf162ae678e462c141b6c21988f509b1120d88 100644 --- a/packages/CLI11/cmake/AddGoogletest.cmake +++ b/packages/CLI11/cmake/AddGoogletest.cmake @@ -4,24 +4,38 @@ # gives output on failed tests without having to set an environment variable. # # -set(UPDATE_DISCONNECTED_IF_AVAILABLE "UPDATE_DISCONNECTED 1") +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +set(BUILD_SHARED_LIBS OFF) -include(DownloadProject) -download_project(PROJ googletest - GIT_REPOSITORY https://github.com/google/googletest.git - GIT_TAG release-1.8.0 - UPDATE_DISCONNECTED 1 - QUIET -) +if(CMAKE_VERSION VERSION_LESS 3.11) + set(UPDATE_DISCONNECTED_IF_AVAILABLE "UPDATE_DISCONNECTED 1") + include(DownloadProject) + download_project(PROJ googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG release-1.8.0 + UPDATE_DISCONNECTED 1 + QUIET + ) + + # CMake warning suppression will not be needed in version 1.9 + set(CMAKE_SUPPRESS_DEVELOPER_WARNINGS 1 CACHE BOOL "") + add_subdirectory(${googletest_SOURCE_DIR} ${googletest_SOURCE_DIR} EXCLUDE_FROM_ALL) +else() + include(FetchContent) + FetchContent_Declare(googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG release-1.8.0) + FetchContent_GetProperties(googletest) + if(NOT googletest_POPULATED) + FetchContent_Populate(googletest) + set(CMAKE_SUPPRESS_DEVELOPER_WARNINGS 1 CACHE BOOL "") + add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR} EXCLUDE_FROM_ALL) + endif() +endif() -set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) -# CMake warning suppression will not be needed in version 1.9 -set(CMAKE_SUPPRESS_DEVELOPER_WARNINGS 1 CACHE BOOL "") -add_subdirectory(${googletest_SOURCE_DIR} ${googletest_SOURCE_DIR} EXCLUDE_FROM_ALL) -unset(CMAKE_SUPPRESS_DEVELOPER_WARNINGS) -if (CMAKE_CONFIGURATION_TYPES) +if(CMAKE_CONFIGURATION_TYPES) add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} --force-new-ctest-process --output-on-failure --build-config "$<CONFIGURATION>") @@ -54,16 +68,17 @@ macro(add_gtest TESTNAME) gtest_add_tests(TARGET ${TESTNAME} TEST_PREFIX "${TESTNAME}." TEST_LIST TmpTestList) + set_tests_properties(${TmpTestList} PROPERTIES FOLDER "Tests") else() gtest_discover_tests(${TESTNAME} TEST_PREFIX "${TESTNAME}." - ) + PROPERTIES FOLDER "Tests") endif() else() add_test(${TESTNAME} ${TESTNAME}) + set_target_properties(${TESTNAME} PROPERTIES FOLDER "Tests") endif() - set_target_properties(${TESTNAME} PROPERTIES FOLDER "Tests") endmacro() @@ -80,3 +95,10 @@ BUILD_GTEST set_target_properties(gtest gtest_main gmock gmock_main PROPERTIES FOLDER "Extern") + +if(MSVC AND MSVC_VERSION GREATER_EQUAL 1900) + target_compile_definitions(gtest PUBLIC _SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING) + target_compile_definitions(gtest_main PUBLIC _SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING) + target_compile_definitions(gmock PUBLIC _SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING) + target_compile_definitions(gmock_main PUBLIC _SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING) +endif() diff --git a/packages/CLI11/conanfile.py b/packages/CLI11/conanfile.py index 77e584f9ad7b3da1070faa52746dcc3ba401b573..7a32f711da704bcd3ddb5d51e1842739fe2fc3bb 100644 --- a/packages/CLI11/conanfile.py +++ b/packages/CLI11/conanfile.py @@ -22,8 +22,8 @@ class HelloConan(ConanFile): def build(self): # this is not building a library, just tests cmake = CMake(self) - cmake.definitions["CLI_EXAMPLES"] = "OFF" - cmake.definitions["CLI_SINGLE_FILE"] = "OFF" + cmake.definitions["CLI11_EXAMPLES"] = "OFF" + cmake.definitions["CLI11_SINGLE_FILE"] = "OFF" cmake.configure() cmake.build() cmake.test() diff --git a/packages/CLI11/docs/Doxyfile b/packages/CLI11/docs/Doxyfile index 5363a26e1f607228ee15e48dcf8e7f87a8d4aada..0e2a8c75b89dfee49740639db146ac14a4837996 100644 --- a/packages/CLI11/docs/Doxyfile +++ b/packages/CLI11/docs/Doxyfile @@ -334,7 +334,7 @@ BUILTIN_STL_SUPPORT = NO # enable parsing support. # The default value is: NO. -CPP_CLI_SUPPORT = NO +CPP_CLI11_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip (see: # http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen diff --git a/packages/CLI11/examples/CMakeLists.txt b/packages/CLI11/examples/CMakeLists.txt index c2eef50cc20589d0c24004347da72be7fd1d3eea..c6851229d4ae826000b1fb3b97a587b3cfa1a141 100644 --- a/packages/CLI11/examples/CMakeLists.txt +++ b/packages/CLI11/examples/CMakeLists.txt @@ -1,5 +1,5 @@ function(add_cli_exe T) - add_executable(${T} ${ARGN} ${CLI_headers}) + add_executable(${T} ${ARGN} ${CLI11_headers}) target_link_libraries(${T} PUBLIC CLI11) set_target_properties( ${T} PROPERTIES @@ -15,11 +15,71 @@ function(add_cli_exe T) endfunction() add_cli_exe(simple simple.cpp) +add_test(NAME simple_basic COMMAND simple) +add_test(NAME simple_all COMMAND simple -f filename.txt -c 12 --flag --flag -d 1.2) +set_property(TEST simple_all PROPERTY PASS_REGULAR_EXPRESSION + "Working on file: filename.txt, direct count: 1, opt count: 1" + "Working on count: 12, direct count: 1, opt count: 1" + "Received flag: 2 (2) times" + "Some value: 1.2") + + add_cli_exe(subcommands subcommands.cpp) +add_test(NAME subcommands_none COMMAND subcommands) +set_property(TEST subcommands_none PROPERTY + PASS_REGULAR_EXPRESSION "A subcommand is required") +add_test(NAME subcommands_all COMMAND subcommands --random start --file name stop --count) +set_property(TEST subcommands_all PROPERTY PASS_REGULAR_EXPRESSION + "Working on --file from start: name" + "Working on --count from stop: 1, direct count: 1" + "Count of --random flag: 1" + "Subcommand: start" + "Subcommand: stop") + add_cli_exe(groups groups.cpp) +add_test(NAME groups_none COMMAND groups) +set_property(TEST groups_none PROPERTY PASS_REGULAR_EXPRESSION + "This is a timer:" + "--file is required" + "Run with --help for more information.") +add_test(NAME groups_all COMMAND groups --file this --count --count -d 1.2) +set_property(TEST groups_all PROPERTY PASS_REGULAR_EXPRESSION + "This is a timer:" + "Working on file: this, direct count: 1, opt count: 1" + "Working on count: 2, direct count: 2, opt count: 2" + "Some value: 1.2") + add_cli_exe(inter_argument_order inter_argument_order.cpp) +add_test(NAME inter_argument_order COMMAND inter_argument_order --foo 1 2 3 --x --bar 4 5 6 --z --foo 7 8) +set_property(TEST inter_argument_order PROPERTY PASS_REGULAR_EXPRESSION + [=[foo : 1 +foo : 2 +foo : 3 +bar : 4 +bar : 5 +bar : 6 +foo : 7 +foo : 8]=]) + add_cli_exe(prefix_command prefix_command.cpp) +add_test(NAME prefix_command COMMAND prefix_command -v 3 2 1 -- other one two 3) +set_property(TEST prefix_command PROPERTY PASS_REGULAR_EXPRESSION + "Prefix: 3 : 2 : 1" + "Remaining commands: other one two 3") + add_cli_exe(enum enum.cpp) +add_test(NAME enum_pass COMMAND enum -l 1) +add_test(NAME enum_fail COMMAND enum -l 4) +set_property(TEST enum_fail PROPERTY PASS_REGULAR_EXPRESSION + "Could not convert: -l,--level = 4") + add_cli_exe(modhelp modhelp.cpp) +add_test(NAME modhelp COMMAND modhelp -a test -h) +set_property(TEST modhelp PROPERTY PASS_REGULAR_EXPRESSION + "Option -a string in help: test") add_subdirectory(subcom_in_files) +add_test(NAME subcom_in_files COMMAND subcommand_main subcommand_a -f this.txt --with-foo) +set_property(TEST subcom_in_files PROPERTY PASS_REGULAR_EXPRESSION + "Working on file: this\.txt" + "Using foo!") diff --git a/packages/CLI11/examples/enum.cpp b/packages/CLI11/examples/enum.cpp index 827bee3925a0f7427d4e9dc4e824b19a6ec7b299..7018681faa76252585e2b249c810a6d8facba606 100644 --- a/packages/CLI11/examples/enum.cpp +++ b/packages/CLI11/examples/enum.cpp @@ -1,12 +1,22 @@ +#include <sstream> #include <CLI/CLI.hpp> -enum Level : std::int32_t { High, Medium, Low }; +enum class Level : int { High, Medium, Low }; + +std::istream &operator>>(std::istream &in, Level &level) { + int i; + in >> i; + level = static_cast<Level>(i); + return in; +} + +std::ostream &operator<<(std::ostream &in, const Level &level) { return in << static_cast<int>(level); } int main(int argc, char **argv) { CLI::App app; Level level; - app.add_set("-l,--level", level, {High, Medium, Low}, "Level settings") + app.add_set("-l,--level", level, {Level::High, Level::Medium, Level::Low}, "Level settings") ->set_type_name("enum/Level in {High=0, Medium=1, Low=2}"); CLI11_PARSE(app, argc, argv); diff --git a/packages/CLI11/examples/inter_argument_order.cpp b/packages/CLI11/examples/inter_argument_order.cpp index 6609fd8be7b59d3f3f89e59ecc8c5c4bbf35b628..9e3c246b9225ffcea72519e5477160da40e340a5 100644 --- a/packages/CLI11/examples/inter_argument_order.cpp +++ b/packages/CLI11/examples/inter_argument_order.cpp @@ -5,15 +5,15 @@ #include <algorithm> int main(int argc, char **argv) { - CLI::App app; + CLI::App app{"An app to practice mixing unlimited arguments, but still recover the original order."}; std::vector<int> foos; - auto foo = app.add_option("--foo,-f", foos); + auto foo = app.add_option("--foo,-f", foos, "Some unlimited argument"); std::vector<int> bars; - auto bar = app.add_option("--bar", bars); + auto bar = app.add_option("--bar", bars, "Some unlimited arggument"); - app.add_flag("--z,--x"); // Random other flags + app.add_flag("--z,--x", "Random other flags"); // Standard parsing lines (copy and paste in, or use CLI11_PARSE) try { @@ -22,7 +22,7 @@ int main(int argc, char **argv) { return app.exit(e); } - // I perfer using the back and popping + // I prefer using the back and popping std::reverse(std::begin(foos), std::end(foos)); std::reverse(std::begin(bars), std::end(bars)); diff --git a/packages/CLI11/examples/modhelp.cpp b/packages/CLI11/examples/modhelp.cpp index 7533bf69c0c5b5364908d2e2c997fce4ac0fb37c..a2568322a93e66ef07396c6667472970002075a8 100644 --- a/packages/CLI11/examples/modhelp.cpp +++ b/packages/CLI11/examples/modhelp.cpp @@ -1,12 +1,11 @@ -// Modify the help print so that argument values are accessible -// Note that this will not shortcut `->required` and other similar options #include "CLI/CLI.hpp" #include <iostream> int main(int argc, char **argv) { - CLI::App test; + CLI::App test{R"raw(Modify the help print so that argument values are accessible. +Note that this will not shortcut `->required` and other similar options.)raw"}; // Remove help flag because it shortcuts all processing test.set_help_flag(); @@ -22,10 +21,10 @@ int main(int argc, char **argv) { if(*help) throw CLI::CallForHelp(); } catch(const CLI::Error &e) { - std::cout << "Option string:" << some_option << std::endl; + std::cout << "Option -a string in help: " << some_option << std::endl; return test.exit(e); } - std::cout << "Option string:" << some_option << std::endl; + std::cout << "Option -a string: " << some_option << std::endl; return 0; } diff --git a/packages/CLI11/examples/prefix_command.cpp b/packages/CLI11/examples/prefix_command.cpp index 3a3a6f0921908c35c1fb54c29f125f354e47e6d7..edd7a0d6852a643b4cd34262e1abfc9c60a83e00 100644 --- a/packages/CLI11/examples/prefix_command.cpp +++ b/packages/CLI11/examples/prefix_command.cpp @@ -6,20 +6,18 @@ int main(int argc, char **argv) { app.prefix_command(); std::vector<int> vals; - app.add_option("--vals,-v", vals)->expected(1); + app.add_option("--vals,-v", vals)->expected(-1); CLI11_PARSE(app, argc, argv); std::vector<std::string> more_comms = app.remaining(); - std::cout << "Prefix:"; + std::cout << "Prefix"; for(int v : vals) - std::cout << v << ":"; + std::cout << ": " << v << " "; std::cout << std::endl << "Remaining commands: "; - // Perfer to loop over from beginning, not "pop" order - std::reverse(std::begin(more_comms), std::end(more_comms)); for(auto com : more_comms) std::cout << com << " "; std::cout << std::endl; diff --git a/packages/CLI11/examples/simple.cpp b/packages/CLI11/examples/simple.cpp index 612fd9e027787dc7bd63ac675512db8a8947f0fe..d1ae4716b8cdee0de8b1d2993a05a21af2e28b2e 100644 --- a/packages/CLI11/examples/simple.cpp +++ b/packages/CLI11/examples/simple.cpp @@ -22,7 +22,7 @@ int main(int argc, char **argv) { << ", opt count: " << opt->count() << std::endl; std::cout << "Working on count: " << count << ", direct count: " << app.count("--count") << ", opt count: " << copt->count() << std::endl; - std::cout << "Recieved flag: " << v << " (" << flag->count() << ") times\n"; + std::cout << "Received flag: " << v << " (" << flag->count() << ") times\n"; std::cout << "Some value: " << value << std::endl; return 0; diff --git a/packages/CLI11/examples/subcom_in_files/CMakeLists.txt b/packages/CLI11/examples/subcom_in_files/CMakeLists.txt index be283a6bdb0a89c97570cf93b05ec95717564596..74ba5dd32c03e5fa6653cbc3a0800c40182caf4b 100644 --- a/packages/CLI11/examples/subcom_in_files/CMakeLists.txt +++ b/packages/CLI11/examples/subcom_in_files/CMakeLists.txt @@ -1 +1 @@ -add_cli_exe(main main.cpp subcommand_a.cpp subcommand_a.hpp) +add_cli_exe(subcommand_main subcommand_main.cpp subcommand_a.cpp subcommand_a.hpp) diff --git a/packages/CLI11/examples/subcom_in_files/main.cpp b/packages/CLI11/examples/subcom_in_files/subcommand_main.cpp similarity index 100% rename from packages/CLI11/examples/subcom_in_files/main.cpp rename to packages/CLI11/examples/subcom_in_files/subcommand_main.cpp diff --git a/packages/CLI11/examples/subcommands.cpp b/packages/CLI11/examples/subcommands.cpp index a28b2aac2ef6fe273caa18d0d51e91eef34939b3..8872d3fcd846b87df781186a266ba3f59603f081 100644 --- a/packages/CLI11/examples/subcommands.cpp +++ b/packages/CLI11/examples/subcommands.cpp @@ -6,6 +6,7 @@ int main(int argc, char **argv) { app.add_flag("--random", "Some random flag"); CLI::App *start = app.add_subcommand("start", "A great subcommand"); CLI::App *stop = app.add_subcommand("stop", "Do you really want to stop?"); + app.require_subcommand(); // 1 or more std::string file; start->add_option("-f,--file", file, "File name"); @@ -14,10 +15,12 @@ int main(int argc, char **argv) { CLI11_PARSE(app, argc, argv); - std::cout << "Working on file: " << file << ", direct count: " << start->count("--file") << std::endl; - std::cout << "Working on count: " << s->count() << ", direct count: " << stop->count("--count") << std::endl; + std::cout << "Working on --file from start: " << file << std::endl; + std::cout << "Working on --count from stop: " << s->count() << ", direct count: " << stop->count("--count") + << std::endl; + std::cout << "Count of --random flag: " << app.count("--random") << std::endl; for(auto subcom : app.get_subcommands()) - std::cout << "Subcommand:" << subcom->get_name() << std::endl; + std::cout << "Subcommand: " << subcom->get_name() << std::endl; return 0; } diff --git a/packages/CLI11/include/CLI/App.hpp b/packages/CLI11/include/CLI/App.hpp index 8fd85e4709e40cf421b7f8a56c3a6c84b4637796..e2c2391945dc739e06fc81576f7b8a7717af2132 100644 --- a/packages/CLI11/include/CLI/App.hpp +++ b/packages/CLI11/include/CLI/App.hpp @@ -19,6 +19,7 @@ // CLI Library includes #include "CLI/Error.hpp" #include "CLI/Ini.hpp" +#include "CLI/Macros.hpp" #include "CLI/Option.hpp" #include "CLI/Split.hpp" #include "CLI/StringTools.hpp" @@ -196,6 +197,9 @@ class App { set_help_flag("-h,--help", "Print this help message and exit"); } + /// virtual destructor + virtual ~App() = default; + /// Set a callback for the end of parsing. /// /// Due to a bug in c++11, @@ -447,7 +451,7 @@ class App { return opt; } -#if __cplusplus >= 201402L +#ifdef CLI11_CPP14 /// Add option for callback (C++14 or better only) Option *add_flag(std::string name, std::function<void(size_t)> function, ///< A function to call, void(size_t) @@ -834,7 +838,7 @@ class App { std::string value; // Non-flags - if(opt->get_expected() != 0) { + if(opt->get_type_size() != 0) { // If the option was found on command line if(opt->count() > 0) @@ -1031,12 +1035,14 @@ class App { /// This gets a vector of pointers with the original parse order const std::vector<Option *> &parse_order() const { return parse_order_; } - /// This retuns the missing options from the current subcommand + /// This returns the missing options from the current subcommand std::vector<std::string> remaining(bool recurse = false) const { std::vector<std::string> miss_list; for(const std::pair<detail::Classifer, std::string> &miss : missing_) { miss_list.push_back(std::get<1>(miss)); } + + // Recurse into subcommands if(recurse) { for(const App *sub : parsed_subcommands_) { std::vector<std::string> output = sub->remaining(recurse); @@ -1048,10 +1054,10 @@ class App { /// This returns the number of remaining options, minus the -- seperator size_t remaining_size(bool recurse = false) const { - size_t count = std::count_if( + size_t count = static_cast<size_t>(std::count_if( std::begin(missing_), std::end(missing_), [](const std::pair<detail::Classifer, std::string> &val) { return val.first != detail::Classifer::POSITIONAL_MARK; - }); + })); if(recurse) { for(const App_p &sub : subcommands_) { count += sub->remaining_size(recurse); @@ -1068,7 +1074,7 @@ class App { /// Currently checks to see if multiple positionals exist with -1 args void _validate() const { auto count = std::count_if(std::begin(options_), std::end(options_), [](const Option_p &opt) { - return opt->get_expected() == -1 && opt->get_positional(); + return opt->get_items_expected() < 0 && opt->get_positional(); }); if(count > 1) throw InvalidError(name_); @@ -1188,8 +1194,8 @@ class App { // Required or partially filled if(opt->get_required() || opt->count() != 0) { // Make sure enough -N arguments parsed (+N is already handled in parsing function) - if(opt->get_expected() < 0 && opt->count() < static_cast<size_t>(-opt->get_expected())) - throw ArgumentMismatch::AtLeast(opt->single_name(), -opt->get_expected()); + if(opt->get_items_expected() < 0 && opt->count() < static_cast<size_t>(-opt->get_items_expected())) + throw ArgumentMismatch::AtLeast(opt->single_name(), -opt->get_items_expected()); // Required but empty if(opt->get_required() && opt->count() == 0) @@ -1259,7 +1265,7 @@ class App { if(op->results_.empty()) { // Flag parsing - if(op->get_expected() == 0) { + if(op->get_type_size() == 0) { if(current.inputs.size() == 1) { std::string val = current.inputs.at(0); val = detail::to_lower(val); @@ -1319,9 +1325,9 @@ class App { size_t _count_remaining_positionals(bool required = false) const { size_t retval = 0; for(const Option_p &opt : options_) - if(opt->get_positional() && (!required || opt->get_required()) && opt->get_expected() > 0 && - static_cast<int>(opt->count()) < opt->get_expected()) - retval = static_cast<size_t>(opt->get_expected()) - opt->count(); + if(opt->get_positional() && (!required || opt->get_required()) && opt->get_items_expected() > 0 && + static_cast<int>(opt->count()) < opt->get_items_expected()) + retval = static_cast<size_t>(opt->get_items_expected()) - opt->count(); return retval; } @@ -1333,7 +1339,7 @@ class App { for(const Option_p &opt : options_) { // Eat options, one by one, until done if(opt->get_positional() && - (static_cast<int>(opt->count()) < opt->get_expected() || opt->get_expected() < 0)) { + (static_cast<int>(opt->count()) < opt->get_items_expected() || opt->get_items_expected() < 0)) { opt->add_result(positional); parse_order_.push_back(opt.get()); @@ -1420,27 +1426,34 @@ class App { // Get a reference to the pointer to make syntax bearable Option_p &op = *op_ptr; - int num = op->get_expected(); + int num = op->get_items_expected(); + // Make sure we always eat the minimum for unlimited vectors + int collected = 0; + + // --this=value if(!value.empty()) { - if(num != -1) + // If exact number expected + if(num > 0) num--; op->add_result(value); parse_order_.push_back(op.get()); + collected += 1; } else if(num == 0) { op->add_result(""); parse_order_.push_back(op.get()); + // -Trest } else if(!rest.empty()) { if(num > 0) num--; op->add_result(rest); parse_order_.push_back(op.get()); rest = ""; + collected += 1; } // Unlimited vector parser if(num < 0) { - int collected = 0; // Make sure we always eat the minimum while(!args.empty() && _recognize(args.back()) == detail::Classifer::NONE) { if(collected >= -num) { // We could break here for allow extras, but we don't @@ -1448,12 +1461,6 @@ class App { // If any positionals remain, don't keep eating if(_count_remaining_positionals() > 0) break; - - // If there are any unlimited positionals, those also take priority - if(std::any_of(std::begin(options_), std::end(options_), [](const Option_p &opt) { - return opt->get_positional() && opt->get_expected() < 0; - })) - break; } op->add_result(args.back()); parse_order_.push_back(op.get()); @@ -1461,6 +1468,10 @@ class App { collected++; } + // Allow -- to end an unlimited list and "eat" it + if(!args.empty() && _recognize(args.back()) == detail::Classifer::POSITIONAL_MARK) + args.pop_back(); + } else { while(num > 0 && !args.empty()) { num--; diff --git a/packages/CLI11/include/CLI/CLI.hpp b/packages/CLI11/include/CLI/CLI.hpp index 53a0cef230346bb583355c02d4c8b0de4ee077d4..662e833dd47d32f029bd564684fb689250383d24 100644 --- a/packages/CLI11/include/CLI/CLI.hpp +++ b/packages/CLI11/include/CLI/CLI.hpp @@ -8,6 +8,10 @@ #include "CLI/Version.hpp" +#include "CLI/Macros.hpp" + +#include "CLI/Optional.hpp" + #include "CLI/StringTools.hpp" #include "CLI/Error.hpp" diff --git a/packages/CLI11/include/CLI/Error.hpp b/packages/CLI11/include/CLI/Error.hpp index bd47806b038e7992bacfc2abc5739e43b0309f2f..09104a651f5ef17ffeb5e2f05f6dc0924aaacfa1 100644 --- a/packages/CLI11/include/CLI/Error.hpp +++ b/packages/CLI11/include/CLI/Error.hpp @@ -91,6 +91,9 @@ class IncorrectConstruction : public ConstructionError { static IncorrectConstruction Set0Opt(std::string name) { return IncorrectConstruction(name + ": Cannot set 0 expected, use a flag instead"); } + static IncorrectConstruction SetFlag(std::string name) { + return IncorrectConstruction(name + ": Cannot set an expected number for flags"); + } static IncorrectConstruction ChangeNotVector(std::string name) { return IncorrectConstruction(name + ": You can only change the expected arguments for vectors"); } @@ -102,7 +105,7 @@ class IncorrectConstruction : public ConstructionError { return IncorrectConstruction("Option " + name + " is not defined"); } static IncorrectConstruction MultiOptionPolicy(std::string name) { - return IncorrectConstruction(name + ": multi_option_policy only works for flags and single value options"); + return IncorrectConstruction(name + ": multi_option_policy only works for flags and exact value options"); } }; diff --git a/packages/CLI11/include/CLI/Macros.hpp b/packages/CLI11/include/CLI/Macros.hpp new file mode 100644 index 0000000000000000000000000000000000000000..9c10b0814aa82360a3477bdec9b0a8dc3b76d598 --- /dev/null +++ b/packages/CLI11/include/CLI/Macros.hpp @@ -0,0 +1,44 @@ +#pragma once + +// Distributed under the 3-Clause BSD License. See accompanying +// file LICENSE or https://github.com/CLIUtils/CLI11 for details. + +namespace CLI { + +// Note that all code in CLI11 must be in a namespace, even if it just a define. + +// The following version macro is very similar to the one in PyBind11 + +#if !(defined(_MSC_VER) && __cplusplus == 199711L) && !defined(__INTEL_COMPILER) +#if __cplusplus >= 201402L +#define CLI11_CPP14 +#if __cplusplus >= 201703L +#define CLI11_CPP17 +#if __cplusplus > 201703L +#define CLI11_CPP20 +#endif +#endif +#endif +#elif defined(_MSC_VER) && __cplusplus == 199711L +// MSVC sets _MSVC_LANG rather than __cplusplus (supposedly until the standard is fully implemented) +// Unless you use the /Zc:__cplusplus flag on Visual Studio 2017 15.7 Preview 3 or newer +#if _MSVC_LANG >= 201402L +#define CLI11_CPP14 +#if _MSVC_LANG > 201402L && _MSC_VER >= 1910 +#define CLI11_CPP17 +#if __MSVC_LANG > 201703L && _MSC_VER >= 1910 +#define CLI11_CPP20 +#endif +#endif +#endif +#endif + +#if defined(PYBIND11_CPP14) +#define CLI11_DEPRECATED(reason) [[deprecated(reason)]] +#elif defined(_MSC_VER) +#define CLI11_DEPRECATED(reason) __declspec(deprecated(reason)) +#else +#define CLI11_DEPRECATED(reason) __attribute__((deprecated(reason))) +#endif + +} // namespace CLI diff --git a/packages/CLI11/include/CLI/Option.hpp b/packages/CLI11/include/CLI/Option.hpp index f8222d3491ccad85b901524b1b155e0197c85714..dd61dfd9dad08e129d64201521d82db468787b05 100644 --- a/packages/CLI11/include/CLI/Option.hpp +++ b/packages/CLI11/include/CLI/Option.hpp @@ -13,6 +13,7 @@ #include <vector> #include "CLI/Error.hpp" +#include "CLI/Macros.hpp" #include "CLI/Split.hpp" #include "CLI/StringTools.hpp" @@ -179,11 +180,13 @@ class Option : public OptionBase<Option> { /// @name Configuration ///@{ - /// The number of expected values, 0 for flag, -1 for unlimited vector - int expected_{1}; + /// The number of arguments that make up one option. -1=unlimited (vector-like), 0=flag, 1=normal option, + /// 2=complex/pair, etc. Set only when the option is created; this is intrinsic to the type. Eventually, -2 may mean + /// vector of pairs. + int type_size_{1}; - /// A private setting to allow args to not be able to accept incorrect expected values - bool changeable_{false}; + /// The number of expected values, type_size_ must be < 0. Ignored for flag. N < 0 means at least -N values. + int expected_{1}; /// A list of validators to run on each value parsed std::vector<std::function<std::string(std::string &)>> validators_; @@ -243,14 +246,25 @@ class Option : public OptionBase<Option> { /// @name Setting options ///@{ - /// Set the number of expected arguments (Flags bypass this) + /// Set the number of expected arguments (Flags don't use this) Option *expected(int value) { - if(expected_ == value) - return this; + // Break if this is a flag + if(type_size_ == 0) + throw IncorrectConstruction::SetFlag(single_name()); + + // Setting 0 is not allowed else if(value == 0) throw IncorrectConstruction::Set0Opt(single_name()); - else if(!changeable_) + + // No change is okay, quit now + else if(expected_ == value) + return this; + + // Type must be a vector + else if(type_size_ >= 0) throw IncorrectConstruction::ChangeNotVector(single_name()); + + // TODO: Can support multioption for non-1 values (except for join) else if(value != 1 && multi_option_policy_ != MultiOptionPolicy::Throw) throw IncorrectConstruction::AfterMultiOpt(single_name()); @@ -299,25 +313,36 @@ class Option : public OptionBase<Option> { return needs(opt1, args...); } -#if __cplusplus <= 201703L +#ifndef CLI11_CPP20 /// Sets required options \deprecated + CLI11_DEPRECATED("Use needs instead of requires (eventual keyword clash)") Option *requires(Option *opt) { return needs(opt); } /// Can find a string if needed \deprecated - template <typename T = App> Option *requires(std::string opt_name) { return needs<T>(opt_name); } + template <typename T = App> Option *requires(std::string opt_name) { + for(const Option_p &opt : dynamic_cast<T *>(parent_)->options_) + if(opt.get() != this && opt->check_name(opt_name)) + return needs(opt.get()); + throw IncorrectConstruction::MissingOption(opt_name); + } /// Any number supported, any mix of string and Opt \deprecated template <typename A, typename B, typename... ARG> Option *requires(A opt, B opt1, ARG... args) { - needs(opt); - return needs(opt1, args...); + requires(opt); + return requires(opt1, args...); } #endif /// Sets excluded options Option *excludes(Option *opt) { - auto tup = excludes_.insert(opt); - if(!tup.second) - throw OptionAlreadyAdded::Excludes(get_name(), opt->get_name()); + excludes_.insert(opt); + + // Help text should be symmetric - excluding a should exclude b + opt->excludes_.insert(this); + + // Ignoring the insert return value, excluding twice is now allowed. + // (Mostly to allow both directions to be excluded by user, even though the library does it for you.) + return this; } @@ -356,9 +381,10 @@ class Option : public OptionBase<Option> { return this; } - /// Take the last argument if given multiple times + /// Take the last argument if given multiple times (or another policy) Option *multi_option_policy(MultiOptionPolicy value = MultiOptionPolicy::Throw) { - if(get_expected() != 0 && get_expected() != 1) + + if(get_items_expected() < 0) throw IncorrectConstruction::MultiOptionPolicy(single_name()); multi_option_policy_ = value; return this; @@ -369,8 +395,32 @@ class Option : public OptionBase<Option> { ///@{ /// The number of arguments the option expects + int get_type_size() const { return type_size_; } + + /// The number of times the option expects to be included int get_expected() const { return expected_; } + /// \breif The total number of expected values (including the type) + /// This is positive if exactly this number is expected, and negitive for at least N values + /// + /// v = fabs(size_type*expected) + /// !MultiOptionPolicy::Throw + /// | Expected < 0 | Expected == 0 | Expected > 0 + /// Size < 0 | -v | 0 | -v + /// Size == 0 | 0 | 0 | 0 + /// Size > 0 | -v | 0 | -v // Expected must be 1 + /// + /// MultiOptionPolicy::Throw + /// | Expected < 0 | Expected == 0 | Expected > 0 + /// Size < 0 | -v | 0 | v + /// Size == 0 | 0 | 0 | 0 + /// Size > 0 | v | 0 | v // Expected must be 1 + /// + int get_items_expected() const { + return std::abs(type_size_ * expected_) * + ((multi_option_policy_ != MultiOptionPolicy::Throw || (expected_ < 0 && type_size_ < 0) ? -1 : 1)); + } + /// True if this has a default value int get_default() const { return default_; } @@ -416,7 +466,7 @@ class Option : public OptionBase<Option> { return out; } - /// The most discriptive name available + /// The most descriptive name available std::string single_name() const { if(!lnames_.empty()) return std::string("--") + lnames_[0]; @@ -444,7 +494,7 @@ class Option : public OptionBase<Option> { std::string help_aftername() const { std::stringstream out; - if(get_expected() != 0) { + if(get_type_size() != 0) { if(!typeval_.empty()) out << " " << typeval_; if(!defaultval_.empty()) @@ -488,20 +538,29 @@ class Option : public OptionBase<Option> { bool local_result; + // Num items expected or length of vector, always at least 1 + // Only valid for a trimming policy + int trim_size = std::min(std::max(std::abs(get_items_expected()), 1), static_cast<int>(results_.size())); + // Operation depends on the policy setting if(multi_option_policy_ == MultiOptionPolicy::TakeLast) { - results_t partial_result = {results_.back()}; + // Allow multi-option sizes (including 0) + results_t partial_result{results_.end() - trim_size, results_.end()}; local_result = !callback_(partial_result); + } else if(multi_option_policy_ == MultiOptionPolicy::TakeFirst) { - results_t partial_result = {results_.at(0)}; + results_t partial_result{results_.begin(), results_.begin() + trim_size}; local_result = !callback_(partial_result); + } else if(multi_option_policy_ == MultiOptionPolicy::Join) { results_t partial_result = {detail::join(results_, "\n")}; local_result = !callback_(partial_result); + } else { - if((expected_ > 0 && results_.size() != static_cast<size_t>(expected_)) || - (expected_ < 0 && results_.size() < static_cast<size_t>(-expected_))) - throw ArgumentMismatch(single_name(), expected_, results_.size()); + // For now, vector of non size 1 types are not supported but possibility included here + if((get_items_expected() > 0 && results_.size() != static_cast<size_t>(get_items_expected())) || + (get_items_expected() < 0 && results_.size() < static_cast<size_t>(-get_items_expected()))) + throw ArgumentMismatch(single_name(), get_items_expected(), results_.size()); else local_result = !callback_(results_); } @@ -583,13 +642,14 @@ class Option : public OptionBase<Option> { /// @name Custom options ///@{ - /// Set a custom option, typestring, expected; locks changeable unless expected is -1 - void set_custom_option(std::string typeval, int expected = 1) { + /// Set a custom option, typestring, type_size + void set_custom_option(std::string typeval, int type_size = 1) { typeval_ = typeval; - expected_ = expected; - if(expected == 0) + type_size_ = type_size; + if(type_size_ == 0) required_ = false; - changeable_ = expected < 0; + if(type_size < 0) + expected_ = -1; } /// Set the default value string representation diff --git a/packages/CLI11/include/CLI/Optional.hpp b/packages/CLI11/include/CLI/Optional.hpp new file mode 100644 index 0000000000000000000000000000000000000000..1597da336a45a3f9944dd5d8ee5f0a50f20b79de --- /dev/null +++ b/packages/CLI11/include/CLI/Optional.hpp @@ -0,0 +1,78 @@ +#pragma once + +// Distributed under the 3-Clause BSD License. See accompanying +// file LICENSE or https://github.com/CLIUtils/CLI11 for details. + +#include <istream> + +#include "CLI/Macros.hpp" + +// [CLI11:verbatim] +#ifdef __has_include +#if defined(CLI11_CPP17) && __has_include(<optional>) +#include <optional> +#ifdef __cpp_lib_optional +#ifndef CLI11_STD_OPTIONAL +#define CLI11_STD_OPTIONAL +#endif +#endif +#endif +#if defined(CLI11_CPP14) && __has_include(<experimental/optional>) +#include <experimental/optional> +#ifndef CLI11_EXPERIMENTAL_OPTIONAL +#define CLI11_EXPERIMENTAL_OPTIONAL +#endif +#endif +#if __has_include(<boost/optional.hpp>) +#include <boost/optional.hpp> +#ifndef CLI11_BOOST_OPTIONAL +#define CLI11_BOOST_OPTIONAL +#endif +#endif +#endif +// [CLI11:verbatim] + +namespace CLI { + +#ifdef CLI11_STD_OPTIONAL +template <typename T> std::istream &operator>>(std::istream &in, std::optional<T> &val) { + T v; + in >> v; + val = v; + return in; +} +#endif + +#ifdef CLI11_EXPERIMENTAL_OPTIONAL +template <typename T> std::istream &operator>>(std::istream &in, std::experimental::optional<T> &val) { + T v; + in >> v; + val = v; + return in; +} +#endif + +#ifdef CLI11_BOOST_OPTIONAL +template <typename T> std::istream &operator>>(std::istream &in, boost::optional<T> &val) { + T v; + in >> v; + val = v; + return in; +} +#endif + +// Export the best optional to the CLI namespace +#if defined(CLI11_STD_OPTIONAL) +using std::optional; +#elif defined(CLI11_EXPERIMENTAL_OPTIONAL) +using std::experimental::optional; +#elif defined(CLI11_BOOST_OPTIONAL) +using boost::optional; +#endif + +// This is true if any optional is found +#if defined(CLI11_STD_OPTIONAL) || defined(CLI11_EXPERIMENTAL_OPTIONAL) || defined(CLI11_BOOST_OPTIONAL) +#define CLI11_OPTIONAL +#endif + +} // namespace CLI diff --git a/packages/CLI11/include/CLI/TypeTools.hpp b/packages/CLI11/include/CLI/TypeTools.hpp index fecdbafd29d6214b54c71495ec144c3df44ea20c..aea4b8568796e94a690a16f7d979357167a17cf1 100644 --- a/packages/CLI11/include/CLI/TypeTools.hpp +++ b/packages/CLI11/include/CLI/TypeTools.hpp @@ -74,8 +74,7 @@ constexpr const char *type_name() { /// Signed integers / enums template <typename T, - enable_if_t<(std::is_integral<T>::value && std::is_signed<T>::value) || std::is_enum<T>::value, - detail::enabler> = detail::dummy> + enable_if_t<(std::is_integral<T>::value && std::is_signed<T>::value), detail::enabler> = detail::dummy> bool lexical_cast(std::string input, T &output) { try { size_t n = 0; @@ -124,7 +123,7 @@ bool lexical_cast(std::string input, T &output) { /// String and similar template <typename T, - enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value && !std::is_enum<T>::value && + enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value && std::is_assignable<T &, std::string>::value, detail::enabler> = detail::dummy> bool lexical_cast(std::string input, T &output) { @@ -134,17 +133,18 @@ bool lexical_cast(std::string input, T &output) { /// Non-string parsable template <typename T, - enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value && !std::is_enum<T>::value && + enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value && !std::is_assignable<T &, std::string>::value, detail::enabler> = detail::dummy> bool lexical_cast(std::string input, T &output) { // On GCC 4.7, thread_local is not available, so this optimization -// is turned off (avoiding multiple initialisations on multiple usages +// is turned off (avoiding multiple initialisations on multiple usages) #if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER) && __GNUC__ == 4 && (__GNUC_MINOR__ < 8) std::istringstream is; #else static thread_local std::istringstream is; + is.clear(); #endif is.str(input); diff --git a/packages/CLI11/include/CLI/Version.hpp b/packages/CLI11/include/CLI/Version.hpp index 40abb162fb896294b4c8c3c929b787feba78981f..009b3f875c0ce2f400335c9e1c34db9b8ad28e37 100644 --- a/packages/CLI11/include/CLI/Version.hpp +++ b/packages/CLI11/include/CLI/Version.hpp @@ -8,8 +8,8 @@ namespace CLI { // Note that all code in CLI11 must be in a namespace, even if it just a define. #define CLI11_VERSION_MAJOR 1 -#define CLI11_VERSION_MINOR 4 +#define CLI11_VERSION_MINOR 5 #define CLI11_VERSION_PATCH 0 -#define CLI11_VERSION "1.4.0" +#define CLI11_VERSION "1.5.0" } // namespace CLI diff --git a/packages/CLI11/scripts/MakeSingleHeader.py b/packages/CLI11/scripts/MakeSingleHeader.py index e6985beb1d8ff10752a5fb913945f0958ba45c9f..d260ab959024a04c52871a0bc3ae6c9b44b143f5 100755 --- a/packages/CLI11/scripts/MakeSingleHeader.py +++ b/packages/CLI11/scripts/MakeSingleHeader.py @@ -1,66 +1,114 @@ #!/usr/bin/env python -# Requires pathlib on python 2 - from __future__ import print_function, unicode_literals import os import re import argparse +import operator +from copy import copy from subprocess import check_output, CalledProcessError +from functools import reduce includes_local = re.compile(r"""^#include "(.*)"$""", re.MULTILINE) includes_system = re.compile(r"""^#include \<(.*)\>$""", re.MULTILINE) +verbatim_tag_str = r""" +^ # Begin of line +[^\n^\[]+ # Some characters, not including [ or the end of a line +\[ # A literal [ +[^\]^\n]* # Anything except a closing ] +CLI11:verbatim # The tag +[^\]^\n]* # Anything except a closing ] +\] # A literal ] +[^\n]* # Up to end of line +$ # End of a line +""" +verbatim_all = re.compile(verbatim_tag_str + "(.*)" + verbatim_tag_str, + re.MULTILINE | re.DOTALL | re.VERBOSE) + +DIR = os.path.dirname(os.path.abspath(__file__)) + +class HeaderFile(object): + TAG = "Unknown git revision" + + def __init__(self, base, inc): + with open(os.path.join(base, inc)) as f: + inner = f.read() -DIR = os.path.dirname(os.path.abspath(__file__)) # Path(__file__).resolve().parent -BDIR = os.path.join(os.path.dirname(DIR), 'include') # DIR.parent / 'include' + # add self.verbatim + if 'CLI11:verbatim' in inner: + self.verbatim = ["\n\n// Verbatim copy from {}".format(inc)] + self.verbatim += verbatim_all.findall(inner) + inner = verbatim_all.sub("", inner) + else: + self.verbatim = [] -print("Git directory:", DIR) + self.headers = set(includes_system.findall(inner)) -try: - TAG = check_output(['git', 'describe', '--tags', '--always'], cwd=str(DIR)).decode("utf-8") -except CalledProcessError: - TAG = "A non-git source" + self.body = '\n// From {}\n\n'.format(inc) + inner[inner.find('namespace'):] -def MakeHeader(out): - main_header = os.path.join(BDIR, 'CLI', 'CLI.hpp') - with open(main_header) as f: - header = f.read() + def __add__(self, other): + out = copy(self) + out.headers |= other.headers + out.body += other.body + out.verbatim += other.verbatim + return out - include_files = includes_local.findall(header) + @property + def header_str(self): + return '\n'.join('#include <'+h+'>' for h in sorted(self.headers)) - headers = set() - output = '' - for inc in include_files: - with open(os.path.join(BDIR, inc)) as f: - inner = f.read() - headers |= set(includes_system.findall(inner)) - output += '\n// From {inc}\n\n'.format(inc=inc) - output += inner[inner.find('namespace'):] - - header_list = '\n'.join('#include <'+h+'>' for h in headers) + @property + def verbatim_str(self): + return '\n'.join(self.verbatim) - output = '''\ + def __str__(self): + return '''\ #pragma once // Distributed under the 3-Clause BSD License. See accompanying // file LICENSE or https://github.com/CLIUtils/CLI11 for details. // This file was generated using MakeSingleHeader.py in CLI11/scripts -// from: {tag} +// from: {self.TAG} // This has the complete CLI library in one file. -{header_list} -{output}'''.format(header_list=header_list, output=output, tag=TAG) +{self.header_str} +{self.verbatim_str} +{self.body} +'''.format(self=self) + + +def MakeHeader(output, main_header, include_dir = '../include'): + # Set tag if possible to class variable + try: + HeaderFile.TAG = check_output(['git', 'describe', '--tags', '--always'], cwd=str(DIR)).decode("utf-8") + except CalledProcessError: + pass - with open(out, 'w') as f: - f.write(output) + base_dir = os.path.abspath(os.path.join(DIR, include_dir)) + main_header = os.path.join(base_dir, main_header) - print("Created {out}".format(out=out)) + with open(main_header) as f: + header = f.read() + + include_files = includes_local.findall(header) + + headers = [HeaderFile(base_dir, inc) for inc in include_files] + single_header = reduce(operator.add, headers) + + with open(output, 'w') as f: + f.write(str(single_header)) + + print("Created", output) if __name__ == '__main__': parser = argparse.ArgumentParser() - parser.add_argument("output", nargs='?', default=os.path.join(BDIR, 'CLI11.hpp')) + parser.add_argument("output", help="Single header file output") + parser.add_argument("--main", default='CLI/CLI.hpp', help="The main include file that defines the other files") + parser.add_argument("--include", default='../include') args = parser.parse_args() - MakeHeader(args.output) + + MakeHeader(args.output, args.main, args.include) + diff --git a/packages/CLI11/scripts/clang-format-pre-commit b/packages/CLI11/scripts/clang-format-pre-commit new file mode 100755 index 0000000000000000000000000000000000000000..822becb06f82c2a0cd8bac4bdc382ed1265384fb --- /dev/null +++ b/packages/CLI11/scripts/clang-format-pre-commit @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +# To use: +# ln -s scripts/clang-format.hook .git/hooks/pre-commit + +# Based loosely on https://github.com/andrewseidl/githook-clang-format + +format_file() { + file="${1}" + case "$file" in + *.hpp | *.cpp | .c | *.cc | *.cu | *.h ) + echo "Fixing: $file" + clang-format -i -style=file "${1}" + git add "${1}" + ;; + *) + ;; + esac +} + +case "${1}" in + --about ) + echo "Runs clang-format on source files" + ;; + * ) + for file in `git diff-index --cached --name-only HEAD` ; do + format_file "${file}" + done + ;; +esac diff --git a/packages/CLI11/tests/AppTest.cpp b/packages/CLI11/tests/AppTest.cpp index e53f9a13530a251a72f9a36973c5de306c83fcf9..5b1e0d92581a7255ec5f9a58040e8d08dab64cc7 100644 --- a/packages/CLI11/tests/AppTest.cpp +++ b/packages/CLI11/tests/AppTest.cpp @@ -1,5 +1,6 @@ #include "app_helper.hpp" #include <cstdlib> +#include <complex> TEST_F(TApp, OneFlagShort) { app.add_flag("-c,--count"); @@ -290,6 +291,40 @@ TEST_F(TApp, JoinOpt2) { EXPECT_EQ(str, "one\ntwo"); } +TEST_F(TApp, TakeLastOptMulti) { + std::vector<int> vals; + app.add_option("--long", vals)->expected(2)->take_last(); + + args = {"--long", "1", "2", "3"}; + + run(); + + EXPECT_EQ(vals, std::vector<int>({2, 3})); +} + +TEST_F(TApp, TakeFirstOptMulti) { + std::vector<int> vals; + app.add_option("--long", vals)->expected(2)->take_first(); + + args = {"--long", "1", "2", "3"}; + + run(); + + EXPECT_EQ(vals, std::vector<int>({1, 2})); +} + +TEST_F(TApp, ComplexOptMulti) { + std::complex<double> val; + app.add_complex("--long", val)->take_first(); + + args = {"--long", "1", "2", "3", "4"}; + + run(); + + EXPECT_DOUBLE_EQ(val.real(), 1); + EXPECT_DOUBLE_EQ(val.imag(), 2); +} + TEST_F(TApp, MissingValueNonRequiredOpt) { int count; app.add_option("-c,--count", count); @@ -318,6 +353,23 @@ TEST_F(TApp, MissingValueMoreThan) { EXPECT_THROW(run(), CLI::ArgumentMismatch); } +TEST_F(TApp, NoMissingValueMoreThan) { + std::vector<int> vals1; + std::vector<int> vals2; + app.add_option("-v", vals1)->expected(-2); + app.add_option("--vals", vals2)->expected(-2); + + args = {"-v", "2", "3", "4"}; + run(); + EXPECT_EQ(vals1, std::vector<int>({2, 3, 4})); + + app.reset(); + + args = {"--vals", "2", "3", "4"}; + run(); + EXPECT_EQ(vals2, std::vector<int>({2, 3, 4})); +} + TEST_F(TApp, NotRequiredOptsSingle) { std::string str; @@ -415,6 +467,52 @@ TEST_F(TApp, RequiredOptsDoubleNeg) { EXPECT_EQ(strs, std::vector<std::string>({"one", "two"})); } +// This makes sure unlimited option priority is +// correct for space vs. no space #90 +TEST_F(TApp, PositionalNoSpace) { + std::vector<std::string> options; + std::string foo, bar; + + app.add_option("-O", options); + app.add_option("foo", foo)->required(); + app.add_option("bar", bar)->required(); + + args = {"-O", "Test", "param1", "param2"}; + run(); + + EXPECT_EQ(options.size(), (size_t)1); + EXPECT_EQ(options.at(0), "Test"); + + app.reset(); + args = {"-OTest", "param1", "param2"}; + run(); + + EXPECT_EQ(options.size(), (size_t)1); + EXPECT_EQ(options.at(0), "Test"); +} + +TEST_F(TApp, PositionalNoSpaceLong) { + std::vector<std::string> options; + std::string foo, bar; + + app.add_option("--option", options); + app.add_option("foo", foo)->required(); + app.add_option("bar", bar)->required(); + + args = {"--option", "Test", "param1", "param2"}; + run(); + + EXPECT_EQ(options.size(), (size_t)1); + EXPECT_EQ(options.at(0), "Test"); + + app.reset(); + args = {"--option=Test", "param1", "param2"}; + run(); + + EXPECT_EQ(options.size(), (size_t)1); + EXPECT_EQ(options.at(0), "Test"); +} + TEST_F(TApp, RequiredOptsUnlimited) { std::vector<std::string> strs; @@ -444,9 +542,23 @@ TEST_F(TApp, RequiredOptsUnlimited) { app.allow_extras(false); std::vector<std::string> remain; app.add_option("positional", remain); + run(); + EXPECT_EQ(strs, std::vector<std::string>({"one", "two"})); + EXPECT_EQ(remain, std::vector<std::string>()); + + app.reset(); + args = {"--str", "one", "--", "two"}; + run(); EXPECT_EQ(strs, std::vector<std::string>({"one"})); EXPECT_EQ(remain, std::vector<std::string>({"two"})); + + app.reset(); + args = {"one", "--str", "two"}; + + run(); + EXPECT_EQ(strs, std::vector<std::string>({"two"})); + EXPECT_EQ(remain, std::vector<std::string>({"one"})); } TEST_F(TApp, RequiredOptsUnlimitedShort) { @@ -478,9 +590,36 @@ TEST_F(TApp, RequiredOptsUnlimitedShort) { app.allow_extras(false); std::vector<std::string> remain; app.add_option("positional", remain); + run(); + EXPECT_EQ(strs, std::vector<std::string>({"one", "two"})); + EXPECT_EQ(remain, std::vector<std::string>()); + + app.reset(); + args = {"-s", "one", "--", "two"}; + run(); EXPECT_EQ(strs, std::vector<std::string>({"one"})); EXPECT_EQ(remain, std::vector<std::string>({"two"})); + + app.reset(); + args = {"one", "-s", "two"}; + + run(); + EXPECT_EQ(strs, std::vector<std::string>({"two"})); + EXPECT_EQ(remain, std::vector<std::string>({"one"})); +} + +TEST_F(TApp, OptsUnlimitedEnd) { + std::vector<std::string> strs; + app.add_option("-s,--str", strs); + app.allow_extras(); + + args = {"one", "-s", "two", "three", "--", "four"}; + + run(); + + EXPECT_EQ(strs, std::vector<std::string>({"two", "three"})); + EXPECT_EQ(app.remaining(), std::vector<std::string>({"one", "four"})); } TEST_F(TApp, RequireOptPriority) { @@ -545,26 +684,6 @@ TEST_F(TApp, NotRequiedExpectedDoubleShort) { EXPECT_THROW(run(), CLI::ArgumentMismatch); } -TEST_F(TApp, EnumTest) { - enum Level : std::int32_t { High, Medium, Low }; - Level level = Level::Low; - app.add_option("--level", level); - - args = {"--level", "1"}; - run(); - EXPECT_EQ(level, Level::Medium); -} - -TEST_F(TApp, NewEnumTest) { - enum class Level2 : std::int32_t { High, Medium, Low }; - Level2 level = Level2::Low; - app.add_option("--level", level); - - args = {"--level", "1"}; - run(); - EXPECT_EQ(level, Level2::Medium); -} - TEST_F(TApp, RequiredFlags) { app.add_flag("-a")->required(); app.add_flag("-b")->mandatory(); // Alternate term @@ -587,24 +706,24 @@ TEST_F(TApp, RequiredFlags) { TEST_F(TApp, CallbackFlags) { - int value = 0; + size_t value = 0; auto func = [&value](size_t x) { value = x; }; app.add_flag_function("-v", func); run(); - EXPECT_EQ(value, 0); + EXPECT_EQ(value, (size_t)0); app.reset(); args = {"-v"}; run(); - EXPECT_EQ(value, 1); + EXPECT_EQ(value, (size_t)1); app.reset(); args = {"-vv"}; run(); - EXPECT_EQ(value, 2); + EXPECT_EQ(value, (size_t)2); EXPECT_THROW(app.add_flag_function("hi", func), CLI::IncorrectConstruction); } @@ -612,24 +731,24 @@ TEST_F(TApp, CallbackFlags) { #if __cplusplus >= 201402L TEST_F(TApp, CallbackFlagsAuto) { - int value = 0; + size_t value = 0; auto func = [&value](size_t x) { value = x; }; app.add_flag("-v", func); run(); - EXPECT_EQ(value, 0); + EXPECT_EQ(value, (size_t)0); app.reset(); args = {"-v"}; run(); - EXPECT_EQ(value, 1); + EXPECT_EQ(value, (size_t)1); app.reset(); args = {"-vv"}; run(); - EXPECT_EQ(value, 2); + EXPECT_EQ(value, (size_t)2); EXPECT_THROW(app.add_flag("hi", func), CLI::IncorrectConstruction); } @@ -1115,7 +1234,7 @@ TEST_F(TApp, NeedsMixedFlags) { run(); } -#if __cplusplus <= 201703L +#ifndef CLI11_CPP20 TEST_F(TApp, RequiresMixedFlags) { CLI::Option *opt1 = app.add_flag("--opt1"); @@ -1380,3 +1499,21 @@ TEST_F(TApp, ThrowingTransform) { EXPECT_EQ(e.what(), std::string("--mess: My Message")); } } + +// #87 +TEST_F(TApp, CustomDoubleOption) { + + std::pair<int, double> custom_opt; + + auto opt = app.add_option("posit", [&custom_opt](CLI::results_t vals) { + custom_opt = {stol(vals.at(0)), stod(vals.at(1))}; + return true; + }); + opt->set_custom_option("INT FLOAT", 2); + + args = {"12", "1.5"}; + + run(); + EXPECT_EQ(custom_opt.first, 12); + EXPECT_DOUBLE_EQ(custom_opt.second, 1.5); +} diff --git a/packages/CLI11/tests/CMakeLists.txt b/packages/CLI11/tests/CMakeLists.txt index e2ae6d2c85e9c4badac2f8e2c85b5defd59b8f90..4c191fcc4e4a7e518f2a1539e49142e77be1cca2 100644 --- a/packages/CLI11/tests/CMakeLists.txt +++ b/packages/CLI11/tests/CMakeLists.txt @@ -1,7 +1,7 @@ -set(GOOGLE_TEST_INDIVIDUAL ON) +set(GOOGLE_TEST_INDIVIDUAL OFF) include(AddGoogletest) -set(CLI_TESTS +set(CLI11_TESTS HelpersTest IniTest SimpleTest @@ -10,22 +10,23 @@ set(CLI_TESTS SubcommandTest HelpTest NewParseTest + OptionalTest ) -set(CLI_MULTIONLY_TESTS +set(CLI11_MULTIONLY_TESTS TimerTest ) # Only affects current directory, so safe include_directories(${CMAKE_CURRENT_SOURCE_DIR}) -foreach(T ${CLI_TESTS}) +foreach(T ${CLI11_TESTS}) - add_executable(${T} ${T}.cpp ${CLI_headers}) + add_executable(${T} ${T}.cpp ${CLI11_headers}) target_link_libraries(${T} PUBLIC CLI11) add_gtest(${T}) - if(CLI_SINGLE_FILE AND CLI_SINGLE_FILE_TESTS) + if(CLI11_SINGLE_FILE AND CLI11_SINGLE_FILE_TESTS) add_executable(${T}_Single ${T}.cpp) target_link_libraries(${T}_Single PUBLIC CLI11_SINGLE) add_gtest(${T}_Single) @@ -36,9 +37,9 @@ foreach(T ${CLI_TESTS}) endforeach() -foreach(T ${CLI_MULTIONLY_TESTS}) +foreach(T ${CLI11_MULTIONLY_TESTS}) - add_executable(${T} ${T}.cpp ${CLI_headers}) + add_executable(${T} ${T}.cpp ${CLI11_headers}) target_link_libraries(${T} PUBLIC CLI11) add_gtest(${T}) @@ -52,3 +53,32 @@ set_target_properties(link_test_1 PROPERTIES FOLDER "Tests") add_executable(link_test_2 link_test_2.cpp) target_link_libraries(link_test_2 PUBLIC CLI11 link_test_1) add_gtest(link_test_2) + +# Add informational printout +# Force this to be in a standard location so CTest can find it +add_executable(informational informational.cpp) +target_link_libraries(informational PUBLIC CLI11) +set_target_properties(informational PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" + RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}" + RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}" + RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO "${CMAKE_BINARY_DIR}" + RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL "${CMAKE_BINARY_DIR}" + ) + +# Adding this printout to CTest +file(WRITE "${PROJECT_BINARY_DIR}/CTestCustom.cmake" + "set(CTEST_CUSTOM_PRE_TEST \"${CMAKE_BINARY_DIR}/informational\")" + ) + +# Add boost to test boost::optional if available +find_package(Boost 1.35) +if(Boost_FOUND) + target_link_libraries(informational PUBLIC Boost::boost) + target_link_libraries(OptionalTest PUBLIC Boost::boost) + + # Enforce Boost::Optional even if __has_include is missing on your compiler + target_compile_definitions(informational PUBLIC CLI11_BOOST_OPTIONAL) + target_compile_definitions(OptionalTest PUBLIC CLI11_BOOST_OPTIONAL) +endif() + diff --git a/packages/CLI11/tests/CreationTest.cpp b/packages/CLI11/tests/CreationTest.cpp index b95e6c0a167706093c3216f02f0aa87d5683cdf3..cee6d3bddfaa18f64d9b41ed35f8b83cf28d2402 100644 --- a/packages/CLI11/tests/CreationTest.cpp +++ b/packages/CLI11/tests/CreationTest.cpp @@ -125,7 +125,7 @@ TEST_F(TApp, IncorrectConstructionFlagPositional3) { TEST_F(TApp, IncorrectConstructionFlagExpected) { auto cat = app.add_flag("--cat"); - EXPECT_NO_THROW(cat->expected(0)); + EXPECT_THROW(cat->expected(0), CLI::IncorrectConstruction); EXPECT_THROW(cat->expected(1), CLI::IncorrectConstruction); } @@ -168,6 +168,13 @@ TEST_F(TApp, IncorrectConstructionNeedsCannotFind) { EXPECT_THROW(cat->needs("--nothing"), CLI::IncorrectConstruction); } +#ifndef CLI11_CPP20 +TEST_F(TApp, IncorrectConstructionRequiresCannotFind) { + auto cat = app.add_flag("--cat"); + EXPECT_THROW(cat->requires("--nothing"), CLI::IncorrectConstruction); +} +#endif + TEST_F(TApp, IncorrectConstructionExcludesCannotFind) { auto cat = app.add_flag("--cat"); EXPECT_THROW(cat->excludes("--nothing"), CLI::IncorrectConstruction); @@ -187,18 +194,20 @@ TEST_F(TApp, IncorrectConstructionDuplicateNeedsTxt) { EXPECT_THROW(cat->needs("--other"), CLI::OptionAlreadyAdded); } -TEST_F(TApp, IncorrectConstructionDuplicateExcludes) { +// Now allowed +TEST_F(TApp, CorrectConstructionDuplicateExcludes) { auto cat = app.add_flag("--cat"); auto other = app.add_flag("--other"); ASSERT_NO_THROW(cat->excludes(other)); - EXPECT_THROW(cat->excludes(other), CLI::OptionAlreadyAdded); + ASSERT_NO_THROW(other->excludes(cat)); } -TEST_F(TApp, IncorrectConstructionDuplicateExcludesTxt) { +// Now allowed +TEST_F(TApp, CorrectConstructionDuplicateExcludesTxt) { auto cat = app.add_flag("--cat"); - app.add_flag("--other"); + auto other = app.add_flag("--other"); ASSERT_NO_THROW(cat->excludes("--other")); - EXPECT_THROW(cat->excludes("--other"), CLI::OptionAlreadyAdded); + ASSERT_NO_THROW(other->excludes("--cat")); } TEST_F(TApp, CheckName) { diff --git a/packages/CLI11/tests/HelpTest.cpp b/packages/CLI11/tests/HelpTest.cpp index 60092d2d35c0c20b757d1d9158e7ec5974a7ab4c..8ac69a832ef5b491eea436a2f3030268179b0d8c 100644 --- a/packages/CLI11/tests/HelpTest.cpp +++ b/packages/CLI11/tests/HelpTest.cpp @@ -1,4 +1,4 @@ -#ifdef CLI_SINGLE_FILE +#ifdef CLI11_SINGLE_FILE #include "CLI11.hpp" #else #include "CLI/CLI.hpp" @@ -203,6 +203,17 @@ TEST(THelp, ExcludesPositional) { EXPECT_THAT(help, HasSubstr("Excludes: op1")); } +TEST(THelp, ExcludesSymmetric) { + CLI::App app{"My prog"}; + + CLI::Option *op1 = app.add_flag("--op1"); + app.add_flag("--op2")->excludes(op1); + + std::string help = app.help(); + + EXPECT_THAT(help, HasSubstr("Excludes: --op2")); +} + TEST(THelp, ManualSetters) { CLI::App app{"My prog"}; @@ -450,3 +461,19 @@ TEST_F(CapturedHelp, RepacedError) { EXPECT_THAT(err.str(), HasSubstr("Thing")); EXPECT_THAT(err.str(), HasSubstr("Usage")); } + +// #87 +TEST(THelp, CustomDoubleOption) { + + std::pair<int, double> custom_opt; + + CLI::App app; + + auto opt = app.add_option("posit", [&custom_opt](CLI::results_t vals) { + custom_opt = {stol(vals.at(0)), stod(vals.at(1))}; + return true; + }); + opt->set_custom_option("INT FLOAT", 2); + + EXPECT_THAT(app.help(), Not(HasSubstr("x 2"))); +} diff --git a/packages/CLI11/tests/HelpersTest.cpp b/packages/CLI11/tests/HelpersTest.cpp index 0f6d87a6090d2cea5eda013dc6e6f6bdeb5aa9e5..281b8f6df2ffecd5e20d88df7f6fe0b1bc0e4b15 100644 --- a/packages/CLI11/tests/HelpersTest.cpp +++ b/packages/CLI11/tests/HelpersTest.cpp @@ -415,8 +415,8 @@ TEST(Types, LexicalCastParsable) { std::complex<double> output; EXPECT_TRUE(CLI::detail::lexical_cast(input, output)); - EXPECT_EQ(output.real(), 4.2); // Doing this in one go sometimes has trouble - EXPECT_EQ(output.imag(), 7.3); // on clang + c++4.8 due to missing const + EXPECT_DOUBLE_EQ(output.real(), 4.2); // Doing this in one go sometimes has trouble + EXPECT_DOUBLE_EQ(output.imag(), 7.3); // on clang + c++4.8 due to missing const EXPECT_FALSE(CLI::detail::lexical_cast(fail_input, output)); EXPECT_FALSE(CLI::detail::lexical_cast(extra_input, output)); diff --git a/packages/CLI11/tests/NewParseTest.cpp b/packages/CLI11/tests/NewParseTest.cpp index 5c38870fc7d650cbe865fb6a19cb4c15922814a5..a465618f46b525f5851997d57fa460567b8b57cc 100644 --- a/packages/CLI11/tests/NewParseTest.cpp +++ b/packages/CLI11/tests/NewParseTest.cpp @@ -34,8 +34,8 @@ TEST_F(TApp, AddingComplexParser) { run(); - EXPECT_EQ(1.5, comp.real()); - EXPECT_EQ(2.5, comp.imag()); + EXPECT_DOUBLE_EQ(1.5, comp.real()); + EXPECT_DOUBLE_EQ(2.5, comp.imag()); } TEST_F(TApp, DefaultComplex) { @@ -48,13 +48,13 @@ TEST_F(TApp, DefaultComplex) { EXPECT_THAT(help, HasSubstr("1")); EXPECT_THAT(help, HasSubstr("2")); - EXPECT_EQ(1, comp.real()); - EXPECT_EQ(2, comp.imag()); + EXPECT_DOUBLE_EQ(1, comp.real()); + EXPECT_DOUBLE_EQ(2, comp.imag()); run(); - EXPECT_EQ(4, comp.real()); - EXPECT_EQ(3, comp.imag()); + EXPECT_DOUBLE_EQ(4, comp.real()); + EXPECT_DOUBLE_EQ(3, comp.imag()); } TEST_F(TApp, BuiltinComplex) { @@ -68,13 +68,13 @@ TEST_F(TApp, BuiltinComplex) { EXPECT_THAT(help, HasSubstr("2")); EXPECT_THAT(help, HasSubstr("COMPLEX")); - EXPECT_EQ(1, comp.real()); - EXPECT_EQ(2, comp.imag()); + EXPECT_DOUBLE_EQ(1, comp.real()); + EXPECT_DOUBLE_EQ(2, comp.imag()); run(); - EXPECT_EQ(4, comp.real()); - EXPECT_EQ(3, comp.imag()); + EXPECT_DOUBLE_EQ(4, comp.real()); + EXPECT_DOUBLE_EQ(3, comp.imag()); } TEST_F(TApp, BuiltinComplexIgnoreI) { @@ -85,8 +85,8 @@ TEST_F(TApp, BuiltinComplexIgnoreI) { run(); - EXPECT_EQ(4, comp.real()); - EXPECT_EQ(3, comp.imag()); + EXPECT_DOUBLE_EQ(4, comp.real()); + EXPECT_DOUBLE_EQ(3, comp.imag()); } TEST_F(TApp, BuiltinComplexFail) { diff --git a/packages/CLI11/tests/OptionalTest.cpp b/packages/CLI11/tests/OptionalTest.cpp new file mode 100644 index 0000000000000000000000000000000000000000..305f42746ae4c900a86e6274782b6190f75f6f4f --- /dev/null +++ b/packages/CLI11/tests/OptionalTest.cpp @@ -0,0 +1,31 @@ +#include <cstdlib> +#include <iostream> + +#include "app_helper.hpp" + +#ifdef CLI11_OPTIONAL + +TEST_F(TApp, OptionalTest) { + CLI::optional<int> opt; + app.add_option("-c,--count", opt); + run(); + EXPECT_FALSE(opt); + + app.reset(); + args = {"-c", "1"}; + run(); + EXPECT_TRUE(opt); + EXPECT_EQ(*opt, 1); + + app.reset(); + args = {"--count", "3"}; + run(); + EXPECT_TRUE(opt); + EXPECT_EQ(*opt, 3); +} + +#else + +TEST_F(TApp, DISABLED_OptionalTest) {} + +#endif diff --git a/packages/CLI11/tests/SimpleTest.cpp b/packages/CLI11/tests/SimpleTest.cpp index bc1f1ba0db655f6b2f25226ac05dab07c76193d1..d87495f3e5e91fa7eeaea291292129e266976a71 100644 --- a/packages/CLI11/tests/SimpleTest.cpp +++ b/packages/CLI11/tests/SimpleTest.cpp @@ -1,4 +1,4 @@ -#ifdef CLI_SINGLE_FILE +#ifdef CLI11_SINGLE_FILE #include "CLI11.hpp" #else #include "CLI/CLI.hpp" diff --git a/packages/CLI11/tests/SubcommandTest.cpp b/packages/CLI11/tests/SubcommandTest.cpp index 5f163f23d41d3d224cf34c97c4d9626c863cb8fe..84fa0ad2d995047b3f4bbbbb277abe6cf3674940 100644 --- a/packages/CLI11/tests/SubcommandTest.cpp +++ b/packages/CLI11/tests/SubcommandTest.cpp @@ -407,6 +407,33 @@ TEST_F(TApp, PrefixProgram) { EXPECT_EQ(app.remaining(), std::vector<std::string>({"other", "--simple", "--mine"})); } +TEST_F(TApp, PrefixNoSeparation) { + + app.prefix_command(); + + std::vector<int> vals; + app.add_option("--vals", vals); + + args = {"--vals", "1", "2", "3", "other"}; + + EXPECT_THROW(run(), CLI::ConversionError); +} + +TEST_F(TApp, PrefixSeparation) { + + app.prefix_command(); + + std::vector<int> vals; + app.add_option("--vals", vals); + + args = {"--vals", "1", "2", "3", "--", "other"}; + + run(); + + EXPECT_EQ(app.remaining(), std::vector<std::string>({"other"})); + EXPECT_EQ(vals, std::vector<int>({1, 2, 3})); +} + TEST_F(TApp, PrefixSubcom) { auto subc = app.add_subcommand("subc"); subc->prefix_command(); diff --git a/packages/CLI11/tests/app_helper.hpp b/packages/CLI11/tests/app_helper.hpp index 49ef7554abf38184549ea7ff984e365a52633702..4280cc9e802277a6bd18652b07d675a5c5b22652 100644 --- a/packages/CLI11/tests/app_helper.hpp +++ b/packages/CLI11/tests/app_helper.hpp @@ -1,6 +1,6 @@ #pragma once -#ifdef CLI_SINGLE_FILE +#ifdef CLI11_SINGLE_FILE #include "CLI11.hpp" #else #include "CLI/CLI.hpp" diff --git a/packages/CLI11/tests/informational.cpp b/packages/CLI11/tests/informational.cpp new file mode 100644 index 0000000000000000000000000000000000000000..89c73ff9aa3040e59a1c9735719c526edaf441ba --- /dev/null +++ b/packages/CLI11/tests/informational.cpp @@ -0,0 +1,50 @@ +#ifdef CLI11_SINGLE_FILE +#include "CLI11.hpp" +#else +#include "CLI/CLI.hpp" +#endif + +#include <iostream> + +int main() { + std::cout << "\nCLI11 information:\n"; + + std::cout << " C++ standard: "; +#if defined(CLI11_CPP20) + std::cout << 20; +#elif defined(CLI11_CPP17) + std::cout << 17; +#elif defined(CLI11_CPP14) + std::cout << 14; +#else + std::cout << 11; +#endif + std::cout << "\n"; + + std::cout << " __has_include: "; +#ifdef __has_include + std::cout << "yes\n"; +#else + std::cout << "no\n"; +#endif + +#ifdef CLI11_OPTIONAL + std::cout << " [Available as CLI::optional]"; +#else + std::cout << " No optional library found\n"; +#endif + +#ifdef CLI11_STD_OPTIONAL + std::cout << " std::optional support active\n"; +#endif + +#ifdef CLI11_EXPERIMENTAL_OPTIONAL + std::cout << " std::experimental::optional support active\n"; +#endif + +#ifdef CLI11_BOOST_OPTIONAL + std::cout << " boost::optional support active\n"; +#endif + + std::cout << std::endl; +} diff --git a/utils/Types.hpp b/utils/Types.hpp new file mode 100644 index 0000000000000000000000000000000000000000..90d0e8407fd037aa7a4cd664ab7431f4753c42a7 --- /dev/null +++ b/utils/Types.hpp @@ -0,0 +1,10 @@ +#ifndef TYPES_HPP +#define TYPES_HPP + +enum class ZeroType { zero }; +constexpr ZeroType zero=ZeroType::zero; + +enum class IdentityType { identity }; +constexpr IdentityType identity=IdentityType::identity; + +#endif // TYPES_HPP