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")