diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml
index 54b942ffb..84ded3c0e 100644
--- a/.github/workflows/windows.yml
+++ b/.github/workflows/windows.yml
@@ -248,7 +248,9 @@ jobs:
#
- # Sign the Windows binaries using our Signpath certificate
+ # Sign the Windows binaries using our Signpath certificate. Note that this involves sending the binary to the
+ # remote SignPath service where the signing actually happens. We need to have an account and credentials with
+ # that service to use it, so this step can't be done in forked repositories.
#
# Various settings here have to align with the "brewtarget" project in the "Brewtarget [OSS]" organisation
# registered at https://app.signpath.io/.
@@ -311,7 +313,15 @@ jobs:
signing-policy-slug: '${{ env.SIGNPATH_SIGNING_POLICY_SLUG }}'
github-artifact-id: '${{steps.upload-unsigned-artifact.outputs.artifact-id}}'
wait-for-completion: true
- output-artifact-directory: 'C:/_/mbuild/packages/windows/signed'
+ #
+ # From trial and error, it seems output-artifact-directory must be a relative directory, because it will get
+ # appended to some base directory (possibly on the remote server where the signing is actually done). Eg, if
+ # we set:
+ # output-artifact-directory: 'C:/_/mbuild/packages/windows/signed'
+ # We get error:
+ # ENOENT: no such file or directory, mkdir 'D:\a\brewtarget\brewtarget\C:\_\mbuild\packages\windows\signed'
+ #
+ output-artifact-directory: 'signed'
github-extended-verification-token: '${{ secrets.EXTENDED_VERIFICATION_TOKEN }}'
#
diff --git a/.signpath/artifact-configurations/default.xml b/.signpath/artifact-configurations/default.xml
index f9bc3800c..45ea99189 100644
--- a/.signpath/artifact-configurations/default.xml
+++ b/.signpath/artifact-configurations/default.xml
@@ -5,32 +5,59 @@
See .github/workflows/windows.yml for more general info on how we use SignPath to sign the Windows binaries
See https://about.signpath.io/documentation/artifact-configuration/ for the syntax for this file
+
+ +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ + NOTE: Although, as is done in the https://github.com/SignPath/github-actions-extended-demo/ SignPath demo
+ + application, we include this Artifact Configuration file in our source tree, it is not directly picked up
+ + by the build process. Whenever you change this file, you need to manually upload the revised version to
+ + the SignPath Brewtarget project, as that's where the configuration is read from during the signing process.
+ +
+ + The upside of doing things this way that the configuration file gets validated when you upload it (including
+ + to confirm you are only using features permitted by your subscription).
+ +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-->
-
+
-
+
diff --git a/CHANGES.markdown b/CHANGES.markdown
index 48692e068..4f5f73877 100644
--- a/CHANGES.markdown
+++ b/CHANGES.markdown
@@ -22,7 +22,8 @@ Minor bug fixes for the 4.0.4 release (ie bugs in 4.0.4 are fixed in this 4.0.5
### Bug Fixes
* Crash on new ingredient import (brewtarget v4.0.4) at startup time [828](https://github.com/Brewtarget/brewtarget/issues/828)
-* Crash on new recipe creation [829](https://github.com/Brewtarget/brewtarget/issues/829)
+* Crash on new recipe creation [829](https://github.com/Brewtarget/brewtarget/issues/829)
+* BeerXML/BeerJSON Recipe export does not match selected type [835](https://github.com/Brewtarget/brewtarget/issues/835)
### Release Timestamp
Tue, 24 Sep 2024 04:00:05 +0100
diff --git a/src/serialization/ImportExport.cpp b/src/serialization/ImportExport.cpp
index 8451103a8..d6f3a3ab5 100755
--- a/src/serialization/ImportExport.cpp
+++ b/src/serialization/ImportExport.cpp
@@ -81,11 +81,30 @@ namespace {
Q_ASSERT(importOrExport == ImportOrExport::EXPORT);
fileChooser.setAcceptMode(QFileDialog::AcceptSave);
fileChooser.setFileMode(QFileDialog::AnyFile);
- // If the user doesn't specify a suffix
- fileChooser.setDefaultSuffix(QString("xml"));
- // TBD what's the difference between filter and nameFilter?
-// QString filterStr = "BeerXML files (*.xml)";
-// fileChooser.setNameFilter(filterStr);
+ //
+ // If the user doesn't specify a suffix, we choose one for them. By default it's "json" to match the first
+ // filter in the list. If the user changes the filter, then we get a signal and change the default suffix
+ // accordingly.
+ //
+ // I think in practice QFileDialog::filterSelected will always get sent when exec() is run below, so we don't
+ // need the call to setDefaultSuffix here as the one in the lambda below will suffice. But it doesn't hurt to
+ // have this initial one, so we'll leave it for now.
+ //
+ fileChooser.setDefaultSuffix(QString("json"));
+ fileChooser.connect(
+ &fileChooser,
+ &QFileDialog::filterSelected,
+ [&fileChooser](QString const & filter) {
+ // The Qt docs are a bit silent about what the parameter is for the QFileDialog::filterSelected signal.
+ // By adding a logging statement here, we discover that we get either "*.json" or "*.xml". So we just
+ // need to chop off the first two characters to get default suffix.
+ //
+ // In Qt 6, we can replace `mid` with `sliced` which is faster apparently.
+ qDebug() << Q_FUNC_INFO << "Export filter is:" << filter;
+ fileChooser.setDefaultSuffix(filter.mid(2));
+ return;
+ }
+ );
}
if (!fileChooser.exec() ) {
@@ -93,8 +112,9 @@ namespace {
return std::nullopt;
}
- qDebug() << Q_FUNC_INFO << "Selected " << fileChooser.selectedFiles().length() << " files";
- qDebug() << Q_FUNC_INFO << "Directory " << fileChooser.directory();
+ qDebug() <<
+ Q_FUNC_INFO << "Selected" << fileChooser.selectedFiles().length() << "file(s) (from directory" <<
+ fileChooser.directory() << "):" << fileChooser.selectedFiles();
// Remember the directory for next time
fileChooserDirectory = fileChooser.directory().canonicalPath();
diff --git a/src/serialization/json/JsonMeasureableUnitsMapping.cpp b/src/serialization/json/JsonMeasureableUnitsMapping.cpp
index 4356b84aa..ad58bfcc2 100755
--- a/src/serialization/json/JsonMeasureableUnitsMapping.cpp
+++ b/src/serialization/json/JsonMeasureableUnitsMapping.cpp
@@ -97,3 +97,37 @@ Measurement::Unit const * JsonMeasureableUnitsMapping::findUnit(std::string_view
Measurement::Unit const * JsonMeasureableUnitsMapping::defaultUnit() const {
return this->nameToUnit.cbegin()->second;
}
+
+template
+S & JsonMeasureableUnitsMapping::writeToStream(S & stream) const {
+ stream <<
+ "Unit Field:" << this->unitField << "; Value Field:" << this->valueField << "; Map:\n";
+ for (auto const & ii : this->nameToUnit) {
+ stream << QString::fromStdString(std::string{ii.first}) << "->" << *ii.second << "\n";
+ }
+ return stream;
+}
+
+template
+S & operator<<(S & stream, JsonMeasureableUnitsMapping const & jmum) {
+ return jmum.writeToStream(stream);
+}
+
+template
+S & operator<<(S & stream, JsonMeasureableUnitsMapping const * jmum) {
+ if (jmum) {
+ stream << *jmum;
+ } else {
+ stream << "NULL";
+ }
+ return stream;
+}
+
+//
+// Instantiate the above template functions for the types that are going to use them
+// (This is all just a trick to allow the template definition to be here in the .cpp file and not in the header.)
+//
+template QDebug & operator<<(QDebug & stream, JsonMeasureableUnitsMapping const & jmum);
+template QTextStream & operator<<(QTextStream & stream, JsonMeasureableUnitsMapping const & jmum);
+template QDebug & operator<<(QDebug & stream, JsonMeasureableUnitsMapping const * jmum);
+template QTextStream & operator<<(QTextStream & stream, JsonMeasureableUnitsMapping const * jmum);
diff --git a/src/serialization/json/JsonMeasureableUnitsMapping.h b/src/serialization/json/JsonMeasureableUnitsMapping.h
index abdda5257..9345f6299 100755
--- a/src/serialization/json/JsonMeasureableUnitsMapping.h
+++ b/src/serialization/json/JsonMeasureableUnitsMapping.h
@@ -89,6 +89,21 @@ class JsonMeasureableUnitsMapping {
* unit matches our canonical metric ones.)
*/
Measurement::Unit const * defaultUnit() const;
+
+ template S & writeToStream(S & stream) const;
};
+/**
+ * \brief Convenience function to allow output of \c JsonMeasureableUnitsMapping to \c QDebug or \c QTextStream stream
+ */
+template
+S & operator<<(S & stream, JsonMeasureableUnitsMapping const & jmum);
+
+/**
+ * \brief Convenience function to allow output of \c JsonMeasureableUnitsMapping to \c QDebug or \c QTextStream stream
+ */
+template
+S & operator<<(S & stream, JsonMeasureableUnitsMapping const * jmum);
+
+
#endif
diff --git a/src/serialization/json/JsonRecord.cpp b/src/serialization/json/JsonRecord.cpp
index f841a9508..7e4431ea2 100755
--- a/src/serialization/json/JsonRecord.cpp
+++ b/src/serialization/json/JsonRecord.cpp
@@ -1081,6 +1081,7 @@ void JsonRecord::insertValue(JsonRecordDefinition::FieldDefinition const & field
JsonMeasureableUnitsMapping const * const unitsMapping =
std::get(fieldDefinition.valueDecoder);
Q_ASSERT(unitsMapping);
+ qDebug() << Q_FUNC_INFO << *unitsMapping;
Measurement::Unit const * const aUnit = unitsMapping->defaultUnit();
Measurement::Unit const & canonicalUnit = aUnit->getCanonical();
qDebug() << Q_FUNC_INFO << canonicalUnit;
diff --git a/src/serialization/json/JsonXPath.cpp b/src/serialization/json/JsonXPath.cpp
index bfc1d3970..234fe84da 100755
--- a/src/serialization/json/JsonXPath.cpp
+++ b/src/serialization/json/JsonXPath.cpp
@@ -496,7 +496,7 @@ std::string JsonXPath::makePointerToLeaf(boost::json::value ** valuePointer) con
return std::get(priorNode);
}
-std::string_view JsonXPath::asKey() const {
+std::string JsonXPath::asKey() const {
// It's a coding error if there is more than one path part
Q_ASSERT(this->m_pathParts.size() == 1);
@@ -505,9 +505,8 @@ std::string_view JsonXPath::asKey() const {
// We need to get the first path part (m_pathParts[0]) and then strip the beginning '/' character from it
// (.substr(1)) before we pass it to the string_view constructor. (Maybe there is a slick way to skip over the '/'
- // in the string_view constructor, but I didn't yet find it.)
- std::string_view key{std::get(this->m_pathParts[0]).substr(1)};
- return key;
+ // in the std::string constructor, but I didn't yet find it.)
+ return std::get(this->m_pathParts[0]).substr(1);
}
char const * JsonXPath::asXPath_c_str() const {
diff --git a/src/serialization/json/JsonXPath.h b/src/serialization/json/JsonXPath.h
index 45ec77287..223a20132 100755
--- a/src/serialization/json/JsonXPath.h
+++ b/src/serialization/json/JsonXPath.h
@@ -122,7 +122,7 @@ class JsonXPath {
* \brief For a trivial path, return it without the leading slash (as a \c string_view because that's what we're
* going to pass to Boost.JSON). Caller's responsibility to ensure this is indeed a trivial path.
*/
- std::string_view asKey() const;
+ std::string asKey() const;
/**
* \brief This returns a C-style string as that's most universally usable for logging