diff --git a/CMakeLists.txt b/CMakeLists.txt index 85921c43..a053f52a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.0) cmake_policy(SET CMP0017 NEW) # need include() with .cmake project(pip) set(_PIP_MAJOR 1) -set(_PIP_MINOR 17) +set(_PIP_MINOR 18) set(_PIP_REVISION 0) set(_PIP_SUFFIX alpha) set(_PIP_COMPANY SHS) diff --git a/README.md b/README.md index 23056ec2..6b6fe825 100644 --- a/README.md +++ b/README.md @@ -30,3 +30,7 @@ You should add ${} to your target. Generate C++ files for resource file. You should add ${} to your target. + +## Documentation + +[Online documentation](https://shs.tools/pip/html/index.html) diff --git a/doc/examples/pistring.cpp b/doc/examples/pistring.cpp index 6ad2423f..0d9323bb 100644 --- a/doc/examples/pistring.cpp +++ b/doc/examples/pistring.cpp @@ -211,6 +211,16 @@ piCout << s.find("3"); // 9 piCout << s.find("3", 4); // 9 piCout << s.find("3", 10); // -1 //! [PIString::findLast] +//! [PIString::findAny] +piCout << PIString("1.str").findAny(".,:"); // 1 +piCout << PIString("1,str").findAny(".,:"); // 1 +piCout << PIString("1:str").findAny(".,:"); // 1 +//! [PIString::findAny] +//! [PIString::findAnyLast] +piCout << PIString("str.0").findAny(".,:"); // 3 +piCout << PIString("str,0").findAny(".,:"); // 3 +piCout << PIString("str:0").findAny(".,:"); // 3 +//! [PIString::findAnyLast] //! [PIString::findWord] PIString s("this is "); piCout << s.find("this"); // 0 diff --git a/src_main/core/pistring.cpp b/src_main/core/pistring.cpp index 3cc9dca2..6e952f93 100755 --- a/src_main/core/pistring.cpp +++ b/src_main/core/pistring.cpp @@ -68,6 +68,48 @@ */ +/*! \fn int versionCompare(const PIString & v0, const PIString & v1, int components = 6) + * \relatesalso PIString + * \brief Compare two version strings in free notation and returns 0, -1 or 1 + * \details This function parse version to number codes and labels. Then it + * compare no more than "components" codes. If there is no difference, compare + * labels. Each label has corresponding integer value, so + * "prealpha" < "alpha" < "prebeta" < "beta" < "rcN" < "" < "rN". + * Example: + * \code + * piCout << versionCompare("1.0.0_rc2-999", "1.0.1_rc2-999"); // -1 + * piCout << versionCompare("1.0.0", "0.9.2"); // 1 + * piCout << versionCompare("1.0.0_r1", "1.0.0"); // 1 + * piCout << versionCompare("1.0.0_r1", "1.0.0", 3); // 0 + * piCout << versionCompare("1.0.0_r1", "1.0.0", 3); // 0 + * piCout << versionCompare(".2-alpha", "0.2_alpha"); // 0 + * piCout << versionCompare("1_prebeta", "1.0_alpha"); // 1 + * \endcode + * \return + * * 0 - equal + * * 1 - v0 > v1 + * * -1 - v0 < v1 + * + * + * \fn PIString versionNormalize(const PIString & v) + * \relatesalso PIString + * \brief Converts version string in free notation to classic view + * \details Parse version as described in \a versionCompare() and + * returns classic view of codes and labels: major.minor.revision[-build][_label]. + * Example: + * \code + * piCout << versionNormalize(""); // 0.0.0 + * piCout << versionNormalize("1"); // 1.0.0 + * piCout << versionNormalize("1.2"); // 1.2.0 + * piCout << versionNormalize("1.2.3"); // 1.2.3 + * piCout << versionNormalize("1.2+rc1.99"); // 1.2.99_rc1 + * piCout << versionNormalize("1.2-alpha"); // 1.2.0_alpha + * piCout << versionNormalize("1..4_rc2-999"); // 1.0.4-999_rc2 + * \endcode + * + */ + + const char PIString::toBaseN[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', @@ -760,6 +802,30 @@ int PIString::findRange(const PIChar & start, const PIChar & end, const PIChar & } +int PIString::findAny(const PIString & str, const int start) const { + for (int i = start; i < length(); ++i) + if (str.contains(at(i))) + return i; + return -1; +} + + +int PIString::findAnyLast(const PIString & str, const int start) const { + for (int i = length() - 1; i >= start; --i) + if (str.contains(at(i))) + return i; + return -1; +} + + +int PIString::entries(const PIChar & c) const { + int sz = size_s(), ret = 0; + for (int i = 0; i < sz; ++i) + if (at(i) == c) ++ret; + return ret; +} + + bool PIString::startsWith(const PIString & str) const { if (size() < str.size()) return false; return str == left(str.size()); @@ -1110,6 +1176,117 @@ inline char chrLwr(char c) { } +const static PIString _versionDelims_ = PIStringAscii("._-+"); + +void parseVersion(PIString s, PIVector & codes, PIStringList & strs) { + s.trim(); + if (s.isEmpty()) { + codes.resize(3, 0); + return; + } + int mccnt = 2 - s.entries('.'); + if (mccnt > 0) { + int ind = s.findLast(".") + 1; + while (!_versionDelims_.contains(s[ind])) { + ++ind; + if (ind > s.size_s() - 1) + break; + } + for (int i = 0; i < mccnt; ++i) + s.insert(ind, ".0"); + } + PIStringList comps; + while (!s.isEmpty()) { + int ind = s.findAny(_versionDelims_); + if (ind >= 0) { + comps << s.takeLeft(ind); + s.cutLeft(1).trim(); + } else { + comps << s; + s.clear(); + } + } + for (int i = 0; i < comps.size_s(); ++i) { + if (comps[i].isEmpty()) + comps[i] = "0"; + bool ok = false; + int val = comps[i].toInt(-1, &ok); + if (ok) { + codes << val; + } else { + strs << comps[i]; + } + } + //piCout << codes << strs; +} + +int versionLabelValue(PIString s) { + int ret = -10000; + if (s.isEmpty()) return 0; + if (s.startsWith("pre")) { + s.cutLeft(3); + ret -= 1; + } + if (s.startsWith("rc")) { + s.cutLeft(2); + ret += s.toInt(); + } + if (s.startsWith("r")) { + s.cutLeft(1); + ret += 10000 + s.toInt(); + } + if (s == "alpha") ret -= 4; + if (s == "beta" ) ret -= 2; + return ret; +} + +int versionCompare(const PIString & v0, const PIString & v1, int components) { + PIStringList strs[2]; PIVector codes[2]; + parseVersion(v0.toLowerCase(), codes[0], strs[0]); + parseVersion(v1.toLowerCase(), codes[1], strs[1]); + //piCout << codes[0] << strs[0]; + int mc = piMaxi(codes[0].size_s(), codes[1].size_s()); + if (codes[0].size_s() < mc) codes[0].resize(mc, 0); + if (codes[1].size_s() < mc) codes[1].resize(mc, 0); + mc = piMaxi(strs[0].size_s(), strs[1].size_s()); + if (strs[0].size_s() < mc) strs[0].resize(mc, ""); + if (strs[1].size_s() < mc) strs[1].resize(mc, ""); + int comps = piMini(components, codes[0].size_s(), codes[1].size_s()); + if (comps < 1) return (v0 == v1 ? 0 : (v0 > v1 ? 1 : -1)); + for (int c = 0; c < comps; ++c) { + if (codes[0][c] > codes[1][c]) return 1; + if (codes[0][c] < codes[1][c]) return -1; + } + mc = piClampi(mc, 0, components - comps); + for (int c = 0; c < mc; ++c) { + int lv0 = versionLabelValue(strs[0][c]); + int lv1 = versionLabelValue(strs[1][c]); + if (lv0 > lv1) return 1; + if (lv0 < lv1) return -1; + } + return 0; +} + + +PIString versionNormalize(const PIString & v) { + PIStringList strs; PIVector codes; + parseVersion(v.toLowerCase(), codes, strs); + PIString ret; + for (int i = 0; i < codes.size_s(); ++i) { + if (i > 0) { + if (i < 3) ret += "."; + else ret += "-"; + } + ret += PIString::fromNumber(codes[i]); + } + for (int i = 0; i < strs.size_s(); ++i) { + ret += "_"; + ret += strs[i]; + } + return ret; +} + + PICout operator <<(PICout s, const PIString & v) { s.space(); s.quote(); @@ -1146,4 +1323,3 @@ PIStringList& PIStringList::removeDuplicates() { } return *this; } - diff --git a/src_main/core/pistring.h b/src_main/core/pistring.h index b6591bf5..c0bf5cc7 100755 --- a/src_main/core/pistring.h +++ b/src_main/core/pistring.h @@ -547,7 +547,29 @@ public: //! \brief Search range between "start" and "end" symbols at index "start_index" and return first occur position. //! \details Example: \snippet pistring.cpp PIString::findRange int findRange(const PIChar & start, const PIChar & end, const PIChar & shield = '\\', const int start_index = 0, int * len = 0) const; - + + //! \brief Search any symbol of "str" from symbol at index "start" and return first occur position + //! \details Example: \snippet pistring.cpp PIString::findAny + int findAny(const PIString & str, const int start = 0) const; + + //! \brief Search any symbol of "str" from symbol at index "start" and return first occur position + //! \details Example: \snippet pistring.cpp PIString::findAny + int findAny(const char * str, const int start = 0) const {return findAny(PIString(str), start);} + + //! \brief Search any symbol of "str" from symbol at index "start" and return last occur position + //! \details Example: \snippet pistring.cpp PIString::findAnyLast + int findAnyLast(const PIString & str, const int start = 0) const; + + //! \brief Search any symbol of "str" from symbol at index "start" and return last occur position + //! \details Example: \snippet pistring.cpp PIString::findAnyLast + int findAnyLast(const char * str, const int start = 0) const {return findAnyLast(PIString(str), start);} + + //! \brief Returns number of occurrences of symbol "c" + int entries(const PIChar & c) const; + + //! \brief Returns number of occurrences of symbol "c" + int entries(char c) const {return entries(PIChar(c));} + //! \brief Return if string starts with "str" bool startsWith(const PIString & str) const; @@ -785,6 +807,11 @@ inline char chrUpr(char c); inline char chrLwr(char c); +int versionCompare(const PIString & v0, const PIString & v1, int components = 6); + +PIString versionNormalize(const PIString & v); + + /*!\brief Strings array class * \details This class is based on \a PIDeque and * expand it functionality. */ diff --git a/src_main/io_devices/piserial.cpp b/src_main/io_devices/piserial.cpp index a83691c6..b0301d2a 100755 --- a/src_main/io_devices/piserial.cpp +++ b/src_main/io_devices/piserial.cpp @@ -143,7 +143,7 @@ * This class provide access to serial device, e.g. COM port. It can read, * write, wait for write. There are several read and write functions. * - * \section PISerial_sec0 FullPath + * \section PISerial_sec1 FullPath * Since version 1.16.0 you can use as \a path DeviceInfo::id() USB identifier. * \code * PISerial * s = new PISerial("0403:6001");