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