diff --git a/src/lay/lay/SaltGrainPropertiesDialog.ui b/src/lay/lay/SaltGrainPropertiesDialog.ui index f503a7832..67551b8e7 100644 --- a/src/lay/lay/SaltGrainPropertiesDialog.ui +++ b/src/lay/lay/SaltGrainPropertiesDialog.ui @@ -279,7 +279,7 @@ - API version + API features Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -842,7 +842,7 @@ - (API version required - i.e. "0.25") + (API version and features - e.g. "0.26; ruby 2.0") diff --git a/src/lay/lay/laySaltGrain.cc b/src/lay/lay/laySaltGrain.cc index fbf3f1a3c..dd6c6944d 100644 --- a/src/lay/lay/laySaltGrain.cc +++ b/src/lay/lay/laySaltGrain.cc @@ -305,6 +305,33 @@ SaltGrain::valid_name (const std::string &n) return res == n; } +bool +SaltGrain::valid_api_version (const std::string &v) +{ + tl::Extractor ex (v.c_str ()); + + while (! ex.at_end ()) { + + std::string feature; + ex.try_read_name (feature); + + bool first = true; + while (! ex.at_end () && ! ex.test (";")) { + int n = 0; + if (! first && ! ex.test (".")) { + return false; + } + if (! ex.try_read (n)) { + return false; + } + first = false; + } + + } + + return true; +} + bool SaltGrain::valid_version (const std::string &v) { diff --git a/src/lay/lay/laySaltGrain.h b/src/lay/lay/laySaltGrain.h index b0df65f81..c30b2b568 100644 --- a/src/lay/lay/laySaltGrain.h +++ b/src/lay/lay/laySaltGrain.h @@ -198,6 +198,11 @@ public: * The API version is the KLayout version required to run the grain's macros. * A version string is of the form "x.y..." where x, y and other version * components are integer numbers. + * + * The version string can also list other features such as ruby version etc. + * The components are separated with a semicolon. + * For example, "0.26; ruby; python 3.0" means: requires KLayout API 0.26, + * ruby (any version) any python (>= 3.0). */ const std::string &api_version () const { @@ -433,6 +438,11 @@ public: */ static bool valid_version (const std::string &v); + /** + * @brief Gets a value indicating whether the given version string is a valid API version string + */ + static bool valid_api_version (const std::string &v); + /** * @brief Checks whether the given string is a valid name */ diff --git a/src/lay/lay/laySaltGrainDetailsTextWidget.cc b/src/lay/lay/laySaltGrainDetailsTextWidget.cc index 16f07e53b..09a329692 100644 --- a/src/lay/lay/laySaltGrainDetailsTextWidget.cc +++ b/src/lay/lay/laySaltGrainDetailsTextWidget.cc @@ -252,7 +252,7 @@ SaltGrainDetailsTextWidget::details_text () stream << "

"; if (! g->api_version ().empty ()) { - stream << "" << QObject::tr ("API version") << ": " << tl::to_qstring (tl::escaped_to_html (g->api_version ())) << " "; + stream << "" << QObject::tr ("API version and features") << ": " << tl::to_qstring (tl::escaped_to_html (g->api_version ())) << " "; } stream << "

"; diff --git a/src/lay/lay/laySaltGrainPropertiesDialog.cc b/src/lay/lay/laySaltGrainPropertiesDialog.cc index f0202aa29..e851f2a3d 100644 --- a/src/lay/lay/laySaltGrainPropertiesDialog.cc +++ b/src/lay/lay/laySaltGrainPropertiesDialog.cc @@ -529,8 +529,8 @@ SaltGrainPropertiesDialog::accept () // API version api_version_alert->clear (); - if (! m_grain.api_version ().empty () && ! SaltGrain::valid_version (m_grain.api_version ())) { - api_version_alert->error () << tr ("'%1' is not a valid API version string. An API version string needs to be numeric (like '0.25'').").arg (tl::to_qstring (m_grain.api_version ())); + if (! m_grain.api_version ().empty () && ! SaltGrain::valid_api_version (m_grain.api_version ())) { + api_version_alert->error () << tr ("'%1' is not a valid API version string. An API version string needs to be a semicolon-separated list of features with optional numeric versions (like '0.26' or 'ruby 2.0; python').").arg (tl::to_qstring (m_grain.api_version ())); } // doc URL diff --git a/src/lay/lay/laySaltManagerDialog.cc b/src/lay/lay/laySaltManagerDialog.cc index 37ff6a90d..5808b0468 100644 --- a/src/lay/lay/laySaltManagerDialog.cc +++ b/src/lay/lay/laySaltManagerDialog.cc @@ -31,6 +31,9 @@ #include "tlString.h" #include "tlExceptions.h" +#include "rba.h" +#include "pya.h" + #include #include #include @@ -108,6 +111,176 @@ private: lay::Salt *mp_salt; }; +// -------------------------------------------------------------------------------------- +// SaltAPIVersionCheck + +class SaltAPIVersionCheck +{ +public: + struct APIFeature + { + APIFeature (const std::string &_name, const std::string &_version, const std::string &_description) + : name (_name), version (_version), description (_description) + { + // .. nothing yet .. + } + + std::string name, version, description; + }; + + SaltAPIVersionCheck (); + bool check (const std::string &api_version); + + const std::string &message () const + { + return m_message; + } + +private: + std::vector m_features; + std::string m_message; + + void populate_features (); + const APIFeature *find_feature (const std::string &name) const; + std::string feature_list () const; +}; + +SaltAPIVersionCheck::SaltAPIVersionCheck () +{ + populate_features (); +} + +bool +SaltAPIVersionCheck::check (const std::string &api_version) +{ + tl::Extractor ex (api_version.c_str ()); + + bool any_not_available = false; + bool good = true; + m_message.clear (); + + while (! ex.at_end ()) { + + std::string fname; + ex.try_read_name (fname); + + std::string v; + while (! ex.at_end () && ! ex.test (";")) { + int n = 0; + if (ex.try_read (n)) { + v += tl::to_string (n); + } else if (ex.test (".")) { + v += "."; + } else { + m_message = tl::to_string (tr ("API version string malformed - cannot check.")); + return false; + } + } + + const APIFeature *f = find_feature (fname); + if (!f) { + + if (! m_message.empty ()) { + m_message += "\n"; + } + m_message += tl::sprintf (tl::to_string (tr ("Feature %s not available.")), fname); + + good = false; + any_not_available = true; + + } else if (! f->version.empty () && ! v.empty () && SaltGrain::compare_versions (f->version, v) < 0) { + + // shorten the version (Python reports "3.6.7 blabla...") + std::vector fv = tl::split (f->version, " "); + tl_assert (! fv.empty ()); + std::string fv_short = fv.front (); + if (fv.size () > 1) { + fv_short += " ..."; + } + + if (! m_message.empty ()) { + m_message += "\n"; + } + m_message += tl::sprintf (tl::to_string (tr ("%s required with version %s or later (is %s).")), f->description, v, fv_short); + + good = false; + + } + + } + + if (any_not_available) { + m_message += tl::sprintf (tl::to_string (tr ("\nAvailable features are: %s.")), feature_list ()); + } + + return good; +} + +std::string +SaltAPIVersionCheck::feature_list () const +{ + std::string fl; + for (std::vector::const_iterator f = m_features.begin (); f != m_features.end (); ++f) { + if (! fl.empty ()) { + fl += ", "; + } + fl += f->name; + } + return fl; +} + +const SaltAPIVersionCheck::APIFeature * +SaltAPIVersionCheck::find_feature (const std::string &name) const +{ + for (std::vector::const_iterator f = m_features.begin (); f != m_features.end (); ++f) { + if (f->name == name) { + return f.operator-> (); + } + } + return 0; +} + +void +SaltAPIVersionCheck::populate_features () +{ + m_features.push_back (APIFeature (std::string (), lay::Version::version (), "KLayout API")); + + if (rba::RubyInterpreter::instance () && rba::RubyInterpreter::instance ()->available ()) { + std::string v = rba::RubyInterpreter::instance ()->version (); + m_features.push_back (APIFeature ("ruby", v, "Ruby")); + if (SaltGrain::compare_versions (v, "2") < 0) { + m_features.push_back (APIFeature ("ruby1", v, "Ruby 1")); + } else if (SaltGrain::compare_versions (v, "3") < 0) { + m_features.push_back (APIFeature ("ruby2", v, "Ruby 2")); + } + } + + if (pya::PythonInterpreter::instance () && pya::PythonInterpreter::instance ()->available ()) { + std::string v = pya::PythonInterpreter::instance ()->version (); + m_features.push_back (APIFeature ("python", v, "Python")); + if (SaltGrain::compare_versions (v, "3") < 0) { + m_features.push_back (APIFeature ("python2", v, "Python 2")); + } else if (SaltGrain::compare_versions (v, "4") < 0) { + m_features.push_back (APIFeature ("python3", v, "Python 3")); + } + } + +#if defined(HAVE_QTBINDINGS) + m_features.push_back (APIFeature ("qt_binding", std::string (), "Qt Binding for RBA or PYA")); +#endif +#if defined(HAVE_QT) +# if QT_VERSION >= 0x040000 && QT_VERSION < 0x050000 + m_features.push_back (APIFeature ("qt4", std::string (), "Qt 4")); +# elif QT_VERSION >= 0x050000 && QT_VERSION < 0x060000 + m_features.push_back (APIFeature ("qt5", std::string (), "Qt 5")); +# endif +#endif + +#if defined(HAVE_64BIT_COORD) + m_features.push_back (APIFeature ("wide-coords", std::string (), "64 bit coordinates")); +#endif +} + // -------------------------------------------------------------------------------------- // SaltManager implementation @@ -790,6 +963,7 @@ SaltManagerDialog::update_models () } + SaltAPIVersionCheck svc; SaltModel *mine_model; mine_model = dynamic_cast (salt_mine_view_update->model ()); @@ -817,8 +991,8 @@ SaltManagerDialog::update_models () // Establish a message indicating whether the API version does not match for (Salt::flat_iterator g = m_salt_mine.begin_flat (); g != m_salt_mine.end_flat (); ++g) { - if (SaltGrain::compare_versions (lay::Version::version (), (*g)->api_version ()) < 0) { - mine_model->set_message ((*g)->name (), SaltModel::Warning, tl::to_string (tr ("This package requires a newer API (%1)").arg (tl::to_qstring ((*g)->api_version ())))); + if (! svc.check ((*g)->api_version ())) { + mine_model->set_message ((*g)->name (), SaltModel::Warning, svc.message ()); mine_model->set_enabled ((*g)->name (), false); } } @@ -848,8 +1022,8 @@ SaltManagerDialog::update_models () // Establish a message indicating whether the API version does not match for (Salt::flat_iterator g = m_salt_mine.begin_flat (); g != m_salt_mine.end_flat (); ++g) { - if (SaltGrain::compare_versions (lay::Version::version (), (*g)->api_version ()) < 0) { - mine_model->set_message ((*g)->name (), SaltModel::Warning, tl::to_string (tr ("This package requires a newer API (%1)").arg (tl::to_qstring ((*g)->api_version ())))); + if (! svc.check ((*g)->api_version ())) { + mine_model->set_message ((*g)->name (), SaltModel::Warning, svc.message ()); mine_model->set_enabled ((*g)->name (), false); } } diff --git a/src/tl/tl/tlString.cc b/src/tl/tl/tlString.cc index 07bbf2210..90ad87b2c 100644 --- a/src/tl/tl/tlString.cc +++ b/src/tl/tl/tlString.cc @@ -1023,6 +1023,15 @@ Extractor::read_word (std::string &value, const char *non_term) return *this; } +Extractor & +Extractor::read_name (std::string &value, const char *non_term) +{ + if (! try_read_name (value, non_term)) { + error (tl::to_string (tr ("Expected a name string"))); + } + return *this; +} + Extractor & Extractor::read_word_or_quoted (std::string &value, const char *non_term) { @@ -1227,6 +1236,31 @@ Extractor::try_read (bool &value) return false; } +bool +Extractor::try_read_name (std::string &string, const char *non_term) +{ + if (! *skip ()) { + return false; + } + + string.clear (); + + // first character must not be a digit + if (*m_cp && (safe_isalpha (*m_cp) || strchr (non_term, *m_cp) != NULL)) { + string += *m_cp; + ++m_cp; + } else { + return false; + } + + while (*m_cp && (safe_isalnum (*m_cp) || strchr (non_term, *m_cp) != NULL)) { + string += *m_cp; + ++m_cp; + } + + return ! string.empty (); +} + bool Extractor::try_read_word (std::string &string, const char *non_term) { @@ -1235,10 +1269,12 @@ Extractor::try_read_word (std::string &string, const char *non_term) } string.clear (); + while (*m_cp && (safe_isalnum (*m_cp) || strchr (non_term, *m_cp) != NULL)) { string += *m_cp; ++m_cp; } + return ! string.empty (); } diff --git a/src/tl/tl/tlString.h b/src/tl/tl/tlString.h index ae8247e89..f8f4ead46 100644 --- a/src/tl/tl/tlString.h +++ b/src/tl/tl/tlString.h @@ -528,6 +528,13 @@ public: */ Extractor &read (std::string &value, const char *term = ""); + /** + * @brief Read a name string + * + * Name strings are like words, but for the first character digits are not allowed. + */ + Extractor &read_name (std::string &value, const char *non_term = "_.$"); + /** * @brief Read a string consisting of "word" characters * @@ -610,6 +617,13 @@ public: */ bool try_read (std::string &string, const char *term = ""); + /** + * @brief Try to read a name string + * + * Name strings are like words, but for the first character digits are not allowed. + */ + bool try_read_name (std::string &value, const char *non_term = "_.$"); + /** * @brief Try to read a string consisting of "word" characters * diff --git a/src/tl/unit_tests/tlString.cc b/src/tl/unit_tests/tlString.cc index 5e049200a..1f95f46f8 100644 --- a/src/tl/unit_tests/tlString.cc +++ b/src/tl/unit_tests/tlString.cc @@ -312,23 +312,49 @@ TEST(8) x = Extractor ("a_word!"); x.read_word (s); EXPECT_EQ (s, "a_word"); + + x = Extractor ("a_word!"); + s.clear (); + x.read_name (s); + EXPECT_EQ (s, "a_word"); EXPECT_EQ (x.test ("!"), true); + x = Extractor ("0_word!"); + EXPECT_EQ (x.try_read_word (s), true); + + x = Extractor ("0_word!"); + EXPECT_EQ (x.try_read_name (s), false); + x = Extractor ("a_word!"); EXPECT_EQ (x.try_read_word (s), true); EXPECT_EQ (s, "a_word"); EXPECT_EQ (x.test ("!"), true); + x = Extractor ("a_word!"); + EXPECT_EQ (x.try_read_name (s), true); + EXPECT_EQ (s, "a_word"); + EXPECT_EQ (x.test ("!"), true); + x = Extractor ("a_word!"); x.read_word (s, "_!"); EXPECT_EQ (s, "a_word!"); EXPECT_EQ (x.at_end (), true); + x = Extractor ("a_word!"); + x.read_name (s, "_!"); + EXPECT_EQ (s, "a_word!"); + EXPECT_EQ (x.at_end (), true); + x = Extractor ("a_word!"); EXPECT_EQ (x.try_read_word (s, "_!"), true); EXPECT_EQ (s, "a_word!"); EXPECT_EQ (x.at_end (), true); + x = Extractor ("a_word!"); + EXPECT_EQ (x.try_read_name (s, "_!"), true); + EXPECT_EQ (s, "a_word!"); + EXPECT_EQ (x.at_end (), true); + x = Extractor ("a_word!"); x.read_word_or_quoted (s); EXPECT_EQ (s, "a_word"); diff --git a/version.sh b/version.sh index ff7a5ae96..5c7d65ca0 100644 --- a/version.sh +++ b/version.sh @@ -2,7 +2,7 @@ # This script is sourced to define the main version parameters # The main version -KLAYOUT_VERSION="0.26" +KLAYOUT_VERSION="0.26.1" # The build date KLAYOUT_VERSION_DATE=$(date "+%Y-%m-%d")