File indexing completed on 2024-12-22 04:15:09

0001 // This file is part of PyKrita, Krita' Python scripting plugin.
0002 //
0003 // SPDX-FileCopyrightText: 2013 Alex Turbov <i.zaufi@gmail.com>
0004 //
0005 // SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only
0006 
0007 #ifndef __VERSION_CHECKER_H__
0008 # define  __VERSION_CHECKER_H__
0009 
0010 #include <libs/global/kis_debug.h>
0011 #include "utilities.h"
0012 # include <QtCore/QString>
0013 # include <QtCore/QStringList>
0014 # include <QtCore/QtGlobal>
0015 
0016 namespace PyKrita
0017 {
0018 
0019 /**
0020  * \brief Class \c version
0021  */
0022 class version
0023 {
0024     enum type {
0025         undefined = -1,
0026         zero = 0
0027     };
0028 
0029 public:
0030     /// Default constructor
0031     explicit version(const int _major = zero, const int _minor = zero, const int patch = zero)
0032         : m_major(_major)
0033         , m_minor(_minor)
0034         , m_patch(patch) {
0035     }
0036 
0037     int _major() const {
0038         return m_major;
0039     }
0040     int _minor() const {
0041         return m_minor;
0042     }
0043     int patch() const {
0044         return m_patch;
0045     }
0046 
0047     bool isValid() const {
0048         return _major() != undefined && _minor() != undefined && patch() != undefined;
0049     }
0050 
0051     operator QString() const {
0052         return QString("%1.%2.%3").arg(_major()).arg(_minor()).arg(patch());
0053     }
0054 
0055     static version fromString(const QString& version_str) {
0056         int tmp[3] = {zero, zero, zero};
0057         QStringList parts = version_str.split('.');
0058         for (
0059             unsigned long i = 0
0060                               ; i < qMin(static_cast<unsigned long>(sizeof(tmp) / sizeof(int)), static_cast<unsigned long>(parts.size()))
0061             ; ++i
0062         ) {
0063             bool ok;
0064             const int num = parts[i].toInt(&ok);
0065             if (ok)
0066                 tmp[i] = num;
0067             else {
0068                 tmp[i] = undefined;
0069                 break;
0070             }
0071         }
0072         return version(tmp[0], tmp[1], tmp[2]);
0073     };
0074 
0075     static version fromPythonObject(PyObject* version_obj)
0076     {
0077         version v = tryObtainVersionFromTuple(version_obj);
0078         if (!v.isValid()) {
0079             // PEP396 requires __version__ to be a tuple of integers,
0080             // but some modules use a string instead.
0081             v = tryObtainVersionFromString(version_obj);
0082         }
0083         return v;
0084     }
0085 
0086     static version invalid() {
0087         static version s_bad(undefined, undefined, undefined);
0088         return s_bad;
0089     }
0090 
0091 private:
0092     int m_major;
0093     int m_minor;
0094     int m_patch;
0095 
0096 
0097     static version tryObtainVersionFromTuple(PyObject* version_obj)
0098     {
0099         Q_ASSERT("Sanity check" && version_obj);
0100 
0101         if (PyTuple_Check(version_obj) == 0)
0102             return version::invalid();
0103 
0104         int version_info[3] = {0, 0, 0};
0105         for (unsigned i = 0; i < PyTuple_Size(version_obj); ++i) {
0106             PyObject* v = PyTuple_GetItem(version_obj, i);
0107             if (v && PyLong_Check(v))
0108                 version_info[i] = PyLong_AsLong(v);
0109             else
0110                 version_info[i] = -1;
0111         }
0112         if (version_info[0] != -1 && version_info[1] != -1 && version_info[2] != -1)
0113             return ::PyKrita::version(version_info[0], version_info[1], version_info[2]);
0114 
0115         return version::invalid();
0116     }
0117 
0118 /**
0119  * Try to parse version string as a simple triplet X.Y.Z.
0120  *
0121  * \todo Some modules has letters in a version string...
0122  * For example current \c pytz version is \e "2013d".
0123  */
0124     static version tryObtainVersionFromString(PyObject* version_obj)
0125     {
0126         Q_ASSERT("Sanity check" && version_obj);
0127 
0128         if (!Python::isUnicode(version_obj))
0129             return version::invalid();
0130 
0131         QString version_str = Python::unicode(version_obj);
0132         if (version_str.isEmpty())
0133             return version::invalid();
0134 
0135         return version::fromString(version_str);
0136     }
0137 
0138 
0139 };
0140 
0141 inline bool operator==(const version& left, const version& right)
0142 {
0143     return left._major() == right._major()
0144            && left._minor() == right._minor()
0145            && left.patch() == right.patch()
0146            ;
0147 }
0148 
0149 inline bool operator!=(const version& left, const version& right)
0150 {
0151     return !(left == right);
0152 }
0153 
0154 inline bool operator<(const version& left, const version& right)
0155 {
0156     return left._major() < right._major()
0157            || (left._major() == right._major() && left._minor() < right._minor())
0158            || (left._major() == right._major() && left._minor() == right._minor() && left.patch() < right.patch())
0159            ;
0160 }
0161 
0162 inline bool operator>(const version& left, const version& right)
0163 {
0164     return left._major() > right._major()
0165            || (left._major() == right._major() && left._minor() > right._minor())
0166            || (left._major() == right._major() && left._minor() == right._minor() && left.patch() > right.patch())
0167            ;
0168 }
0169 
0170 inline bool operator<=(const version& left, const version& right)
0171 {
0172     return left == right || left < right;
0173 }
0174 
0175 inline bool operator>=(const version& left, const version& right)
0176 {
0177     return left == right || left > right;
0178 }
0179 
0180 
0181 /**
0182 * \brief Class \c version_checker
0183 */
0184 class version_checker
0185 {
0186 public:
0187     enum operation {
0188         invalid
0189         , undefined
0190         , less
0191         , less_or_equal
0192         , greater
0193         , greater_or_equal
0194         , not_equal
0195         , equal
0196         , last__
0197     };
0198 
0199     /// Default constructor
0200     explicit version_checker(const operation op = invalid)
0201         : m_op(op) {
0202     }
0203 
0204     bool isValid() const {
0205         return m_op != invalid;
0206     }
0207 
0208     bool isEmpty() const {
0209         return m_op == undefined;
0210     }
0211 
0212     void bind_second(const version& rhs) {
0213         m_rhs = rhs;
0214     }
0215 
0216     bool operator()(const version& left) {
0217         switch (m_op) {
0218         case less:
0219             return left < m_rhs;
0220         case greater:
0221             return left > m_rhs;
0222         case equal:
0223             return left == m_rhs;
0224         case not_equal:
0225             return left != m_rhs;
0226         case less_or_equal:
0227             return left <= m_rhs;
0228         case greater_or_equal:
0229             return left >= m_rhs;
0230         default:
0231             Q_ASSERT(!"Sanity check");
0232             break;
0233         }
0234         return false;
0235     }
0236 
0237     version required() const {
0238         return m_rhs;
0239     }
0240 
0241     QString operationToString() const {
0242         QString result;
0243         switch (m_op) {
0244         case less:
0245             result = " < ";
0246             break;
0247         case greater:
0248             result = " > ";
0249             break;
0250         case equal:
0251             result = " = ";
0252             break;
0253         case not_equal:
0254             result = " != ";
0255             break;
0256         case less_or_equal:
0257             result = " <= ";
0258             break;
0259         case greater_or_equal:
0260             result = " >= ";
0261             break;
0262         default:
0263             Q_ASSERT(!"Sanity check");
0264             break;
0265         }
0266         return result;
0267     }
0268 
0269     static version_checker fromString(const QString& version_info) {
0270         version_checker checker(invalid);
0271         if (version_info.isEmpty())
0272             return checker;
0273 
0274         bool lookup_next_char = false;
0275         int strip_lead_pos = 0;
0276         switch (version_info.at(0).toLatin1()) {
0277         case '<':
0278             checker.m_op = less;
0279             lookup_next_char = true;
0280             break;
0281         case '>':
0282             checker.m_op = greater;
0283             lookup_next_char = true;
0284             break;
0285         case '=':
0286             strip_lead_pos = 1;
0287             checker.m_op = equal;
0288             break;
0289         default:
0290             strip_lead_pos = 0;
0291             checker.m_op = equal;
0292             break;
0293         }
0294         if (lookup_next_char) {
0295             if (version_info.at(1).toLatin1() == '=') {
0296                 // NOTE Shift state
0297                 checker.m_op = operation(int(checker.m_op) + 1);
0298                 strip_lead_pos = 2;
0299             } else {
0300                 strip_lead_pos = 1;
0301             }
0302         }
0303         //
0304         QString rhs_str = version_info.mid(strip_lead_pos).trimmed();
0305         version rhs = version::fromString(rhs_str);
0306         if (rhs.isValid())
0307             checker.bind_second(rhs);
0308         else
0309             checker.m_op = invalid;
0310         return checker;
0311     }
0312 
0313 private:
0314     operation m_op;
0315     version m_rhs;
0316 };
0317 
0318 }                                                           // namespace PyKrita
0319 #endif                                                      //  __VERSION_CHECKER_H__