Improved infrastructure for add-ons: C++ classes, metadata file, Nasal interface
This commit adds C++ classes for add-on management, most notably AddonManager, Addon and AddonVersion. The AddonManager is used to register add-ons. It relies on an std::map<std::string, AddonRef> to hold the metadata of each registered add-on (keys of the std::map are add-on identifiers, and AddonRef is currently SGSharedPtr<Addon>). Accessor methods are available for: - retrieving the list of registered or loaded add-ons (terminology explained in $FG_ROOT/Docs/README.add-ons); - checking if a particular add-on has already been registered or loaded; - for each add-on, obtaining an Addon instance which can be queried for its name, id, version, base path, the minimum and maximum FlightGear versions it requires, its base node in the Property Tree, its order in the load sequence, short and long description strings, home page, etc. The most important metadata is made accessible in the Property Tree under /addons/by-id/<addon-id> and the property /addons/by-id/<addon-id>/loaded can be checked or listened to, in order to determine when a particular add-on is loaded. There is also a Nasal interface to access add-on metadata in a convenient way. In order to provide this metadata, each add-on must from now on have in its base directory a file called 'addon-metadata.xml'. All this is documented in much more detail in $FG_ROOT/Docs/README.add-ons. Mailing-list discussion: https://sourceforge.net/p/flightgear/mailman/message/36146017/
This commit is contained in:
parent
ac50a3c7ed
commit
48f52f14c4
18 changed files with 2015 additions and 28 deletions
197
src/Add-ons/Addon.cxx
Normal file
197
src/Add-ons/Addon.cxx
Normal file
|
@ -0,0 +1,197 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
//
|
||||
// Addon.cxx --- FlightGear class holding add-on metadata
|
||||
// Copyright (C) 2017 Florent Rougon
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include <simgear/misc/sg_path.hxx>
|
||||
#include <simgear/nasal/cppbind/Ghost.hxx>
|
||||
#include <simgear/nasal/cppbind/NasalHash.hxx>
|
||||
#include <simgear/nasal/naref.h>
|
||||
|
||||
#include <Main/globals.hxx>
|
||||
#include <Scripting/NasalSys.hxx>
|
||||
|
||||
#include "addon_fwd.hxx"
|
||||
#include "Addon.hxx"
|
||||
#include "AddonVersion.hxx"
|
||||
|
||||
using std::string;
|
||||
|
||||
namespace flightgear
|
||||
{
|
||||
|
||||
Addon::Addon(std::string id, AddonVersion version, SGPath basePath,
|
||||
std::string minFGVersionRequired, std::string maxFGVersionRequired,
|
||||
SGPropertyNode* addonNode)
|
||||
: _id(std::move(id)),
|
||||
_version(new AddonVersion(std::move(version))),
|
||||
_basePath(std::move(basePath)),
|
||||
_minFGVersionRequired(std::move(minFGVersionRequired)),
|
||||
_maxFGVersionRequired(std::move(maxFGVersionRequired)),
|
||||
_addonNode(addonNode)
|
||||
{
|
||||
if (_minFGVersionRequired.empty()) {
|
||||
// This add-on metadata class appeared in FlightGear 2017.4.0
|
||||
_minFGVersionRequired = "2017.4.0";
|
||||
}
|
||||
|
||||
if (_maxFGVersionRequired.empty()) {
|
||||
_maxFGVersionRequired = "none"; // special value
|
||||
}
|
||||
}
|
||||
|
||||
Addon::Addon()
|
||||
: Addon(std::string(), AddonVersion(), SGPath(), std::string(),
|
||||
std::string(), nullptr)
|
||||
{ }
|
||||
|
||||
std::string Addon::getId() const
|
||||
{ return _id; }
|
||||
|
||||
void Addon::setId(const std::string& addonId)
|
||||
{ _id = addonId; }
|
||||
|
||||
std::string Addon::getName() const
|
||||
{ return _name; }
|
||||
|
||||
void Addon::setName(const std::string& addonName)
|
||||
{ _name = addonName; }
|
||||
|
||||
AddonVersionRef Addon::getVersion() const
|
||||
{ return _version; }
|
||||
|
||||
void Addon::setVersion(const AddonVersion& addonVersion)
|
||||
{ _version.reset(new AddonVersion(addonVersion)); }
|
||||
|
||||
std::string Addon::getShortDescription() const
|
||||
{ return _shortDescription; }
|
||||
|
||||
void Addon::setShortDescription(const std::string& addonShortDescription)
|
||||
{ _shortDescription = addonShortDescription; }
|
||||
|
||||
std::string Addon::getLongDescription() const
|
||||
{ return _longDescription; }
|
||||
|
||||
void Addon::setLongDescription(const std::string& addonLongDescription)
|
||||
{ _longDescription = addonLongDescription; }
|
||||
|
||||
SGPath Addon::getBasePath() const
|
||||
{ return _basePath; }
|
||||
|
||||
void Addon::setBasePath(const SGPath& addonBasePath)
|
||||
{ _basePath = addonBasePath; }
|
||||
|
||||
std::string Addon::getMinFGVersionRequired() const
|
||||
{ return _minFGVersionRequired; }
|
||||
|
||||
void Addon::setMinFGVersionRequired(const string& minFGVersionRequired)
|
||||
{ _minFGVersionRequired = minFGVersionRequired; }
|
||||
|
||||
std::string Addon::getMaxFGVersionRequired() const
|
||||
{ return _maxFGVersionRequired; }
|
||||
|
||||
void Addon::setMaxFGVersionRequired(const string& maxFGVersionRequired)
|
||||
{
|
||||
if (maxFGVersionRequired.empty()) {
|
||||
_maxFGVersionRequired = "none"; // special value
|
||||
} else {
|
||||
_maxFGVersionRequired = maxFGVersionRequired;
|
||||
}
|
||||
}
|
||||
|
||||
std::string Addon::getHomePage() const
|
||||
{ return _homePage; }
|
||||
|
||||
void Addon::setHomePage(const std::string& addonHomePage)
|
||||
{ _homePage = addonHomePage; }
|
||||
|
||||
std::string Addon::getDownloadUrl() const
|
||||
{ return _downloadUrl; }
|
||||
|
||||
void Addon::setDownloadUrl(const std::string& addonDownloadUrl)
|
||||
{ _downloadUrl = addonDownloadUrl; }
|
||||
|
||||
std::string Addon::getSupportUrl() const
|
||||
{ return _supportUrl; }
|
||||
|
||||
void Addon::setSupportUrl(const std::string& addonSupportUrl)
|
||||
{ _supportUrl = addonSupportUrl; }
|
||||
|
||||
SGPropertyNode_ptr Addon::getAddonNode() const
|
||||
{ return _addonNode; }
|
||||
|
||||
void Addon::setAddonNode(SGPropertyNode* addonNode)
|
||||
{ _addonNode = SGPropertyNode_ptr(addonNode); }
|
||||
|
||||
naRef Addon::getAddonPropsNode() const
|
||||
{
|
||||
FGNasalSys* nas = globals->get_subsystem<FGNasalSys>();
|
||||
return nas->wrappedPropsNode(_addonNode.get());
|
||||
}
|
||||
|
||||
SGPropertyNode_ptr Addon::getLoadedFlagNode() const
|
||||
{
|
||||
return { _addonNode->getChild("loaded", 0, 1) };
|
||||
}
|
||||
|
||||
int Addon::getLoadSequenceNumber() const
|
||||
{ return _loadSequenceNumber; }
|
||||
|
||||
void Addon::setLoadSequenceNumber(int num)
|
||||
{ _loadSequenceNumber = num; }
|
||||
|
||||
std::string Addon::str() const
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << "addon '" << _id << "' (version = " << *_version
|
||||
<< ", base path = '" << _basePath.utf8Str()
|
||||
<< "', minFGVersionRequired = '" << _minFGVersionRequired
|
||||
<< "', maxFGVersionRequired = '" << _maxFGVersionRequired << "')";
|
||||
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
// Static method
|
||||
void Addon::setupGhost(nasal::Hash& addonsModule)
|
||||
{
|
||||
nasal::Ghost<AddonRef>::init("addons.Addon")
|
||||
.member("id", &Addon::getId)
|
||||
.member("name", &Addon::getName)
|
||||
.member("version", &Addon::getVersion)
|
||||
.member("shortDescription", &Addon::getShortDescription)
|
||||
.member("longDescription", &Addon::getLongDescription)
|
||||
.member("basePath", &Addon::getBasePath)
|
||||
.member("minFGVersionRequired", &Addon::getMinFGVersionRequired)
|
||||
.member("maxFGVersionRequired", &Addon::getMaxFGVersionRequired)
|
||||
.member("homePage", &Addon::getHomePage)
|
||||
.member("downloadUrl", &Addon::getDownloadUrl)
|
||||
.member("supportUrl", &Addon::getSupportUrl)
|
||||
.member("node", &Addon::getAddonPropsNode)
|
||||
.member("loadSequenceNumber", &Addon::getLoadSequenceNumber);
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const Addon& addon)
|
||||
{
|
||||
return os << addon.str();
|
||||
}
|
||||
|
||||
} // of namespace flightgear
|
135
src/Add-ons/Addon.hxx
Normal file
135
src/Add-ons/Addon.hxx
Normal file
|
@ -0,0 +1,135 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
//
|
||||
// Addon.hxx --- FlightGear class holding add-on metadata
|
||||
// Copyright (C) 2017 Florent Rougon
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#ifndef FG_ADDON_HXX
|
||||
#define FG_ADDON_HXX
|
||||
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
#include <simgear/misc/sg_path.hxx>
|
||||
#include <simgear/nasal/cppbind/NasalHash.hxx>
|
||||
#include <simgear/nasal/naref.h>
|
||||
#include <simgear/props/props.hxx>
|
||||
#include <simgear/structure/SGReferenced.hxx>
|
||||
|
||||
#include "addon_fwd.hxx"
|
||||
#include "AddonVersion.hxx"
|
||||
|
||||
namespace flightgear
|
||||
{
|
||||
|
||||
class Addon : public SGReferenced {
|
||||
public:
|
||||
// Default constructor. 'minFGVersionRequired' is initialized to "2017.4.0"
|
||||
// and 'maxFGVersionRequired' to "none".
|
||||
Addon();
|
||||
// An empty value for 'minFGVersionRequired' is translated into "2017.4.0".
|
||||
// An empty value for 'maxFGVersionRequired' is translated into "none".
|
||||
Addon(std::string id, AddonVersion version, SGPath basePath,
|
||||
std::string minFGVersionRequired = "",
|
||||
std::string maxFGVersionRequired = "",
|
||||
SGPropertyNode* addonNode = nullptr);
|
||||
|
||||
std::string getId() const;
|
||||
void setId(const std::string& addonId);
|
||||
|
||||
std::string getName() const;
|
||||
void setName(const std::string& addonName);
|
||||
|
||||
AddonVersionRef getVersion() const;
|
||||
void setVersion(const AddonVersion& addonVersion);
|
||||
|
||||
std::string getShortDescription() const;
|
||||
void setShortDescription(const std::string& addonShortDescription);
|
||||
|
||||
std::string getLongDescription() const;
|
||||
void setLongDescription(const std::string& addonLongDescription);
|
||||
|
||||
SGPath getBasePath() const;
|
||||
void setBasePath(const SGPath& addonBasePath);
|
||||
|
||||
// Should be valid for use with simgear::strutils::compare_versions()
|
||||
std::string getMinFGVersionRequired() const;
|
||||
void setMinFGVersionRequired(const std::string& minFGVersionRequired);
|
||||
|
||||
// Should be valid for use with simgear::strutils::compare_versions(),
|
||||
// except for the special value "none".
|
||||
std::string getMaxFGVersionRequired() const;
|
||||
void setMaxFGVersionRequired(const std::string& maxFGVersionRequired);
|
||||
|
||||
std::string getHomePage() const;
|
||||
void setHomePage(const std::string& addonHomePage);
|
||||
|
||||
std::string getDownloadUrl() const;
|
||||
void setDownloadUrl(const std::string& addonDownloadUrl);
|
||||
|
||||
std::string getSupportUrl() const;
|
||||
void setSupportUrl(const std::string& addonSupportUrl);
|
||||
|
||||
// Node pertaining to the add-on in the Global Property Tree
|
||||
SGPropertyNode_ptr getAddonNode() const;
|
||||
void setAddonNode(SGPropertyNode* addonNode);
|
||||
// For Nasal: result as a props.Node object
|
||||
naRef getAddonPropsNode() const;
|
||||
|
||||
// Property node indicating whether the add-on is fully loaded
|
||||
SGPropertyNode_ptr getLoadedFlagNode() const;
|
||||
|
||||
// 0 for the first loaded add-on, 1 for the second, etc.
|
||||
// -1 means “not set” (as done by the default constructor)
|
||||
int getLoadSequenceNumber() const;
|
||||
void setLoadSequenceNumber(int num);
|
||||
|
||||
// Simple string representation
|
||||
std::string str() const;
|
||||
|
||||
static void setupGhost(nasal::Hash& addonsModule);
|
||||
|
||||
private:
|
||||
// The add-on identifier, in reverse DNS style. The AddonManager refuses to
|
||||
// register two add-ons with the same id in a given FlightGear session.
|
||||
std::string _id;
|
||||
// Pretty name for the add-on (not constrained to reverse DNS style)
|
||||
std::string _name;
|
||||
// Use a smart pointer to expose the AddonVersion instance to Nasal without
|
||||
// needing to copy the data every time.
|
||||
AddonVersionRef _version;
|
||||
// Strings describing what the add-on does
|
||||
std::string _shortDescription;
|
||||
std::string _longDescription;
|
||||
SGPath _basePath;
|
||||
// To be used with simgear::strutils::compare_versions()
|
||||
std::string _minFGVersionRequired;
|
||||
// Ditto, but there is a special value: "none"
|
||||
std::string _maxFGVersionRequired;
|
||||
std::string _homePage;
|
||||
std::string _downloadUrl;
|
||||
std::string _supportUrl;
|
||||
// Main node for the add-on in the Property Tree
|
||||
SGPropertyNode_ptr _addonNode;
|
||||
// Semantics explained above
|
||||
int _loadSequenceNumber = -1;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const Addon& addonMetaData);
|
||||
|
||||
} // of namespace flightgear
|
||||
|
||||
#endif // of FG_ADDON_HXX
|
404
src/Add-ons/AddonManager.cxx
Normal file
404
src/Add-ons/AddonManager.cxx
Normal file
|
@ -0,0 +1,404 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
//
|
||||
// AddonManager.cxx --- Manager class for FlightGear add-ons
|
||||
// Copyright (C) 2017 Florent Rougon
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cassert>
|
||||
|
||||
#include <simgear/debug/logstream.hxx>
|
||||
#include <simgear/misc/sg_path.hxx>
|
||||
#include <simgear/misc/strutils.hxx>
|
||||
#include <simgear/props/props.hxx>
|
||||
#include <simgear/props/props_io.hxx>
|
||||
#include <simgear/structure/exception.hxx>
|
||||
|
||||
#include <Include/version.h>
|
||||
#include <Main/fg_props.hxx>
|
||||
#include <Main/globals.hxx>
|
||||
|
||||
#include "addon_fwd.hxx"
|
||||
#include "Addon.hxx"
|
||||
#include "AddonManager.hxx"
|
||||
#include "AddonVersion.hxx"
|
||||
|
||||
namespace strutils = simgear::strutils;
|
||||
|
||||
using std::string;
|
||||
using std::vector;
|
||||
using std::shared_ptr;
|
||||
using std::unique_ptr;
|
||||
|
||||
namespace flightgear
|
||||
{
|
||||
|
||||
static unique_ptr<AddonManager> staticInstance;
|
||||
|
||||
namespace addon_errors
|
||||
{
|
||||
// ***************************************************************************
|
||||
// * Base class for custom exceptions *
|
||||
// ***************************************************************************
|
||||
|
||||
// Prepending a prefix such as "Add-on error: " would be redundant given the
|
||||
// messages used below.
|
||||
error::error(const string& message, const string& origin)
|
||||
: sg_exception(message, origin)
|
||||
{ }
|
||||
|
||||
error::error(const char* message, const char* origin)
|
||||
: error(string(message), string(origin))
|
||||
{ }
|
||||
|
||||
} // of namespace addon_errors
|
||||
|
||||
// ***************************************************************************
|
||||
// * AddonManager *
|
||||
// ***************************************************************************
|
||||
|
||||
// Static method
|
||||
const unique_ptr<AddonManager>&
|
||||
AddonManager::createInstance()
|
||||
{
|
||||
SG_LOG(SG_GENERAL, SG_DEBUG, "Initializing the AddonManager");
|
||||
|
||||
staticInstance.reset(new AddonManager());
|
||||
return staticInstance;
|
||||
}
|
||||
|
||||
// Static method
|
||||
const unique_ptr<AddonManager>&
|
||||
AddonManager::instance()
|
||||
{
|
||||
return staticInstance;
|
||||
}
|
||||
|
||||
// Static method
|
||||
void
|
||||
AddonManager::reset()
|
||||
{
|
||||
SG_LOG(SG_GENERAL, SG_DEBUG, "Resetting the AddonManager");
|
||||
staticInstance.reset();
|
||||
}
|
||||
|
||||
// Static method
|
||||
void
|
||||
AddonManager::loadConfigFileIfExists(const SGPath& configFile)
|
||||
{
|
||||
if (!configFile.exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
readProperties(configFile, globals->get_props());
|
||||
} catch (const sg_exception &e) {
|
||||
throw addon_errors::error_loading_config_file(
|
||||
"unable to load add-on config file '" + configFile.utf8Str() + "': " +
|
||||
e.getFormattedMessage());
|
||||
}
|
||||
|
||||
SG_LOG(SG_GENERAL, SG_INFO,
|
||||
"Loaded add-on config file: '" << configFile.utf8Str() + "'");
|
||||
}
|
||||
|
||||
string
|
||||
AddonManager::registerAddonMetadata(const SGPath& addonPath)
|
||||
{
|
||||
SGPropertyNode addonRoot;
|
||||
const SGPath metadataFile = addonPath / "addon-metadata.xml";
|
||||
|
||||
if (!metadataFile.exists()) {
|
||||
throw addon_errors::no_metadata_file_found(
|
||||
"unable to find add-on metadata file '" + metadataFile.utf8Str() + "'");
|
||||
}
|
||||
|
||||
try {
|
||||
readProperties(metadataFile, &addonRoot);
|
||||
} catch (const sg_exception &e) {
|
||||
throw addon_errors::error_loading_metadata_file(
|
||||
"unable to load add-on metadata file '" + metadataFile.utf8Str() + "': " +
|
||||
e.getFormattedMessage());
|
||||
}
|
||||
|
||||
// Check the format version of the metadata file
|
||||
SGPropertyNode *fmtVersionNode = addonRoot.getChild("format-version");
|
||||
if (fmtVersionNode == nullptr) {
|
||||
throw addon_errors::error_loading_metadata_file(
|
||||
"no 'format-version' node found in add-on metadata file '" +
|
||||
metadataFile.utf8Str() + "'");
|
||||
}
|
||||
|
||||
int formatVersion = fmtVersionNode->getIntValue();
|
||||
if (formatVersion != 1) {
|
||||
throw addon_errors::error_loading_metadata_file(
|
||||
"unknown format version in add-on metadata file '" +
|
||||
metadataFile.utf8Str() + "': " + std::to_string(formatVersion));
|
||||
}
|
||||
|
||||
// Now the data we are really interested in
|
||||
SGPropertyNode *addonNode = addonRoot.getChild("addon");
|
||||
if (addonNode == nullptr) {
|
||||
throw addon_errors::error_loading_metadata_file(
|
||||
"no 'addon' node found in add-on metadata file '" +
|
||||
metadataFile.utf8Str() + "'");
|
||||
}
|
||||
|
||||
SGPropertyNode *idNode = addonNode->getChild("identifier");
|
||||
if (idNode == nullptr) {
|
||||
throw addon_errors::error_loading_metadata_file(
|
||||
"no 'identifier' node found in add-on metadata file '" +
|
||||
metadataFile.utf8Str() + "'");
|
||||
}
|
||||
std::string addonId = strutils::strip(idNode->getStringValue());
|
||||
|
||||
// Require a non-empty identifier for the add-on
|
||||
if (addonId.empty()) {
|
||||
throw addon_errors::error_loading_metadata_file(
|
||||
"empty or whitespace-only value for the 'identifier' node in add-on "
|
||||
"metadata file '" + metadataFile.utf8Str() + "'");
|
||||
} else if (addonId.find('.') == string::npos) {
|
||||
SG_LOG(SG_GENERAL, SG_WARN,
|
||||
"Add-on identifier '" << addonId << "' does not use reverse DNS "
|
||||
"style (e.g., org.flightgear.addons.MyAddon) in add-on metadata "
|
||||
"file '" << metadataFile.utf8Str() + "'");
|
||||
}
|
||||
|
||||
SGPropertyNode *nameNode = addonNode->getChild("name");
|
||||
if (nameNode == nullptr) {
|
||||
throw addon_errors::error_loading_metadata_file(
|
||||
"no 'name' node found in add-on metadata file '" +
|
||||
metadataFile.utf8Str() + "'");
|
||||
}
|
||||
std::string addonName = strutils::strip(nameNode->getStringValue());
|
||||
|
||||
// Require a non-empty name for the add-on
|
||||
if (addonName.empty()) {
|
||||
throw addon_errors::error_loading_metadata_file(
|
||||
"empty or whitespace-only value for the 'name' node in add-on "
|
||||
"metadata file '" + metadataFile.utf8Str() + "'");
|
||||
}
|
||||
|
||||
SGPropertyNode *versionNode = addonNode->getChild("version");
|
||||
if (versionNode == nullptr) {
|
||||
throw addon_errors::error_loading_metadata_file(
|
||||
"no 'version' node found in add-on metadata file '" +
|
||||
metadataFile.utf8Str() + "'");
|
||||
}
|
||||
AddonVersion addonVersion(versionNode->getStringValue());
|
||||
|
||||
std::string addonShortDescription;
|
||||
SGPropertyNode *shortDescNode = addonNode->getChild("short-description");
|
||||
if (shortDescNode != nullptr) {
|
||||
addonShortDescription = strutils::strip(shortDescNode->getStringValue());
|
||||
}
|
||||
|
||||
std::string addonLongDescription;
|
||||
SGPropertyNode *longDescNode = addonNode->getChild("long-description");
|
||||
if (longDescNode != nullptr) {
|
||||
addonLongDescription = strutils::strip(longDescNode->getStringValue());
|
||||
}
|
||||
|
||||
std::string addonMinFGVersionRequired;
|
||||
SGPropertyNode *minNode = addonNode->getChild("min-FG-version");
|
||||
if (minNode != nullptr) {
|
||||
addonMinFGVersionRequired = minNode->getStringValue();
|
||||
}
|
||||
|
||||
std::string addonMaxFGVersionRequired;
|
||||
SGPropertyNode *maxNode = addonNode->getChild("max-FG-version");
|
||||
if (maxNode != nullptr) {
|
||||
addonMaxFGVersionRequired = maxNode->getStringValue();
|
||||
}
|
||||
|
||||
std::string addonHomePage;
|
||||
SGPropertyNode *homePageNode = addonNode->getChild("home-page");
|
||||
if (homePageNode != nullptr) {
|
||||
addonHomePage = strutils::strip(homePageNode->getStringValue());
|
||||
}
|
||||
|
||||
std::string addonDownloadUrl;
|
||||
SGPropertyNode *downloadUrlNode = addonNode->getChild("download-url");
|
||||
if (downloadUrlNode != nullptr) {
|
||||
addonDownloadUrl = strutils::strip(downloadUrlNode->getStringValue());
|
||||
}
|
||||
|
||||
std::string addonSupportUrl;
|
||||
SGPropertyNode *supportUrlNode = addonNode->getChild("support-url");
|
||||
if (supportUrlNode != nullptr) {
|
||||
addonSupportUrl = strutils::strip(supportUrlNode->getStringValue());
|
||||
}
|
||||
|
||||
SGPropertyNode* addonPropNode = fgGetNode("addons", true)
|
||||
->getChild("by-id", 0, 1)
|
||||
->getChild(addonId, 0, 1);
|
||||
// Object holding all the add-on metadata
|
||||
AddonRef addon(new Addon(
|
||||
addonId, std::move(addonVersion), addonPath,
|
||||
addonMinFGVersionRequired, addonMaxFGVersionRequired,
|
||||
addonPropNode));
|
||||
addon->setName(addonName);
|
||||
addon->setShortDescription(addonShortDescription);
|
||||
addon->setLongDescription(addonLongDescription);
|
||||
addon->setHomePage(addonHomePage);
|
||||
addon->setDownloadUrl(addonDownloadUrl);
|
||||
addon->setSupportUrl(addonSupportUrl);
|
||||
addon->setLoadSequenceNumber(_loadSequenceNumber++);
|
||||
|
||||
// Check that the FlightGear version satisfies the add-on requirements
|
||||
std::string minFGversion = addon->getMinFGVersionRequired();
|
||||
if (strutils::compare_versions(FLIGHTGEAR_VERSION, minFGversion) < 0) {
|
||||
throw addon_errors::fg_version_too_old(
|
||||
"add-on '" + addonId + "' requires FlightGear " + minFGversion +
|
||||
" or later, however this is FlightGear " + FLIGHTGEAR_VERSION);
|
||||
}
|
||||
|
||||
std::string maxFGversion = addon->getMaxFGVersionRequired();
|
||||
if (maxFGversion != "none" &&
|
||||
strutils::compare_versions(FLIGHTGEAR_VERSION, maxFGversion) > 0) {
|
||||
throw addon_errors::fg_version_too_recent(
|
||||
"add-on '" + addonId + "' requires FlightGear " + maxFGversion +
|
||||
" or earlier, however this is FlightGear " + FLIGHTGEAR_VERSION);
|
||||
}
|
||||
|
||||
// Store the add-on metadata in _idToAddonMap
|
||||
auto emplaceRetval = _idToAddonMap.emplace(addonId, std::move(addon));
|
||||
|
||||
// Prevent registration of two add-ons with the same id
|
||||
if (!emplaceRetval.second) {
|
||||
auto existingElt = _idToAddonMap.find(addonId);
|
||||
assert(existingElt != _idToAddonMap.end());
|
||||
throw addon_errors::duplicate_registration_attempt(
|
||||
"attempt to register add-on '" + addonId + "' with base path '"
|
||||
+ addonPath.utf8Str() + "', however it is already registered with base "
|
||||
"path '" + existingElt->second->getBasePath().utf8Str() + "'");
|
||||
}
|
||||
|
||||
SG_LOG(SG_GENERAL, SG_DEBUG,
|
||||
"Loaded add-on metadata file: '" << metadataFile.utf8Str() + "'");
|
||||
|
||||
return addonId;
|
||||
}
|
||||
|
||||
string
|
||||
AddonManager::registerAddon(const SGPath& addonPath)
|
||||
{
|
||||
// Use realpath() as in FGGlobals::append_aircraft_path(), otherwise
|
||||
// fgValidatePath() will deny access to resources under the add-on path if
|
||||
// one of its components is a symlink.
|
||||
const SGPath addonRealPath = addonPath.realpath();
|
||||
const string addonId = registerAddonMetadata(addonRealPath);
|
||||
loadConfigFileIfExists(addonRealPath / "config.xml");
|
||||
globals->append_aircraft_path(addonRealPath);
|
||||
|
||||
AddonRef addon{getAddon(addonId)};
|
||||
addon->getLoadedFlagNode()->setBoolValue(false);
|
||||
SGPropertyNode_ptr addonNode = addon->getAddonNode();
|
||||
|
||||
// Set a few properties for the add-on under this node
|
||||
addonNode->getNode("id", true)->setStringValue(addonId);
|
||||
addonNode->getNode("name", true)->setStringValue(addon->getName());
|
||||
addonNode->getNode("version", true)
|
||||
->setStringValue(addonVersion(addonId)->str());
|
||||
addonNode->getNode("path", true)->setStringValue(addonRealPath.utf8Str());
|
||||
addonNode->getNode("load-seq-num", true)
|
||||
->setIntValue(addon->getLoadSequenceNumber());
|
||||
|
||||
// “Legacy node”. Should we remove these two lines?
|
||||
SGPropertyNode* seqNumNode = fgGetNode("addons", true)->addChild("addon");
|
||||
seqNumNode->getNode("path", true)->setStringValue(addonRealPath.utf8Str());
|
||||
|
||||
string msg = "Registered add-on '" + addon->getName() + "' (" + addonId +
|
||||
") version " + addonVersion(addonId)->str() + "; "
|
||||
"base path is '" + addonRealPath.utf8Str() + "'";
|
||||
// This preserves the registration order
|
||||
_registeredAddons.push_back(std::move(addon));
|
||||
SG_LOG(SG_GENERAL, SG_INFO, msg);
|
||||
|
||||
return addonId;
|
||||
}
|
||||
|
||||
bool
|
||||
AddonManager::isAddonRegistered(const string& addonId) const
|
||||
{
|
||||
return (_idToAddonMap.find(addonId) != _idToAddonMap.end());
|
||||
}
|
||||
|
||||
bool
|
||||
AddonManager::isAddonLoaded(const string& addonId) const
|
||||
{
|
||||
return (isAddonRegistered(addonId) &&
|
||||
getAddon(addonId)->getLoadedFlagNode()->getBoolValue());
|
||||
}
|
||||
|
||||
vector<AddonRef>
|
||||
AddonManager::registeredAddons() const
|
||||
{
|
||||
return _registeredAddons;
|
||||
}
|
||||
|
||||
vector<AddonRef>
|
||||
AddonManager::loadedAddons() const
|
||||
{
|
||||
vector<AddonRef> v;
|
||||
v.reserve(_idToAddonMap.size()); // will be the right size most of the times
|
||||
|
||||
for (const auto& elem: _idToAddonMap) {
|
||||
if (isAddonLoaded(elem.first)) {
|
||||
v.push_back(elem.second);
|
||||
}
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
AddonRef
|
||||
AddonManager::getAddon(const string& addonId) const
|
||||
{
|
||||
const auto it = _idToAddonMap.find(addonId);
|
||||
|
||||
if (it == _idToAddonMap.end()) {
|
||||
throw sg_exception("tried to get add-on '" + addonId + "', however no "
|
||||
"such add-on has been registered.");
|
||||
}
|
||||
|
||||
return it->second;
|
||||
}
|
||||
|
||||
AddonVersionRef
|
||||
AddonManager::addonVersion(const string& addonId) const
|
||||
{
|
||||
return getAddon(addonId)->getVersion();
|
||||
}
|
||||
|
||||
SGPath
|
||||
AddonManager::addonBasePath(const string& addonId) const
|
||||
{
|
||||
return getAddon(addonId)->getBasePath();
|
||||
}
|
||||
|
||||
SGPropertyNode_ptr AddonManager::addonNode(const string& addonId) const
|
||||
{
|
||||
return getAddon(addonId)->getAddonNode();
|
||||
}
|
||||
|
||||
} // of namespace flightgear
|
136
src/Add-ons/AddonManager.hxx
Normal file
136
src/Add-ons/AddonManager.hxx
Normal file
|
@ -0,0 +1,136 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
//
|
||||
// AddonManager.hxx --- Manager class for FlightGear add-ons
|
||||
// Copyright (C) 2017 Florent Rougon
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#ifndef FG_ADDONMANAGER_HXX
|
||||
#define FG_ADDONMANAGER_HXX
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <memory> // std::unique_ptr, std::shared_ptr
|
||||
#include <vector>
|
||||
|
||||
#include <simgear/misc/sg_path.hxx>
|
||||
#include <simgear/props/props.hxx>
|
||||
|
||||
#include "addon_fwd.hxx"
|
||||
#include "Addon.hxx"
|
||||
#include "AddonVersion.hxx"
|
||||
|
||||
namespace flightgear
|
||||
{
|
||||
|
||||
namespace addon_errors
|
||||
{
|
||||
// Custom exception classes
|
||||
class error : public sg_exception
|
||||
{
|
||||
public:
|
||||
explicit error(const std::string& message,
|
||||
const std::string& origin = std::string());
|
||||
explicit error(const char* message, const char* origin = nullptr);
|
||||
};
|
||||
|
||||
class error_loading_config_file : public error
|
||||
{ using error::error; /* inherit all constructors */ };
|
||||
|
||||
class no_metadata_file_found : public error
|
||||
{ using error::error; };
|
||||
|
||||
class error_loading_metadata_file : public error
|
||||
{ using error::error; };
|
||||
|
||||
class duplicate_registration_attempt : public error
|
||||
{ using error::error; };
|
||||
|
||||
class fg_version_too_old : public error
|
||||
{ using error::error; };
|
||||
|
||||
class fg_version_too_recent : public error
|
||||
{ using error::error; };
|
||||
|
||||
} // of namespace addon_errors
|
||||
|
||||
class AddonManager
|
||||
{
|
||||
public:
|
||||
AddonManager(const AddonManager&) = delete;
|
||||
AddonManager& operator=(const AddonManager&) = delete;
|
||||
AddonManager(AddonManager&&) = delete;
|
||||
AddonManager& operator=(AddonManager&&) = delete;
|
||||
// The instance is created by createInstance() -> private constructor
|
||||
// but it should be deleted by its owning std::unique_ptr -> public destructor
|
||||
~AddonManager() = default;
|
||||
|
||||
// Static creator
|
||||
static const std::unique_ptr<AddonManager>& createInstance();
|
||||
// Singleton accessor
|
||||
static const std::unique_ptr<AddonManager>& instance();
|
||||
// Reset the static smart pointer, i.e., shut down the AddonManager.
|
||||
static void reset();
|
||||
|
||||
// Register an add-on and return its id.
|
||||
// 'addonPath': directory containing the add-on to register
|
||||
//
|
||||
// This comprises the following steps, where $path = addonPath.realpath():
|
||||
// - load add-on metadata from $path/addon-metadata.xml and register it
|
||||
// inside _idToAddonMap (this step is done via registerAddonMetadata());
|
||||
// - load $path/config.xml into the Property Tree;
|
||||
// - append $path to the list of aircraft paths;
|
||||
// - make part of the add-on metadata available in the Property Tree under
|
||||
// the /addons node (/addons/by-id/<addonId>/{id,version,path,...});
|
||||
// - append a ref to the Addon instance to _registeredAddons.
|
||||
std::string registerAddon(const SGPath& addonPath);
|
||||
// Return the list of registered add-ons in registration order (which, BTW,
|
||||
// is the same as load order).
|
||||
std::vector<AddonRef> registeredAddons() const;
|
||||
bool isAddonRegistered(const std::string& addonId) const;
|
||||
|
||||
// A loaded add-on is one whose main.nas file has been loaded. The returned
|
||||
// vector is sorted by add-on id (cheap sorting based on UTF-8 code units,
|
||||
// only guaranteed correct for ASCII chars).
|
||||
std::vector<AddonRef> loadedAddons() const;
|
||||
bool isAddonLoaded(const std::string& addonId) const;
|
||||
|
||||
AddonRef getAddon(const std::string& addonId) const;
|
||||
AddonVersionRef addonVersion(const std::string& addonId) const;
|
||||
SGPath addonBasePath(const std::string& addonId) const;
|
||||
|
||||
// Base node pertaining to the add-on in the Global Property Tree
|
||||
SGPropertyNode_ptr addonNode(const std::string& addonId) const;
|
||||
|
||||
private:
|
||||
// Constructor called from createInstance() only
|
||||
explicit AddonManager() = default;
|
||||
|
||||
static void loadConfigFileIfExists(const SGPath& configFile);
|
||||
// Register add-on metadata inside _idToAddonMap and return the add-on id
|
||||
std::string registerAddonMetadata(const SGPath& addonPath);
|
||||
|
||||
// Map each add-on id to the corresponding Addon instance.
|
||||
std::map<std::string, AddonRef> _idToAddonMap;
|
||||
// The order in _registeredAddons is the registration order.
|
||||
std::vector<AddonRef> _registeredAddons;
|
||||
// 0 for the first loaded add-on, 1 for the second, etc.
|
||||
// Also note that add-ons are loaded in their registration order.
|
||||
int _loadSequenceNumber = 0;
|
||||
};
|
||||
|
||||
} // of namespace flightgear
|
||||
|
||||
#endif // of FG_ADDONMANAGER_HXX
|
396
src/Add-ons/AddonVersion.cxx
Normal file
396
src/Add-ons/AddonVersion.cxx
Normal file
|
@ -0,0 +1,396 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
//
|
||||
// AddonVersion.cxx --- Version class for FlightGear add-ons
|
||||
// Copyright (C) 2017 Florent Rougon
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#include <numeric> // std::accumulate()
|
||||
#include <ostream>
|
||||
#include <regex>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include <simgear/misc/strutils.hxx>
|
||||
#include <simgear/nasal/cppbind/Ghost.hxx>
|
||||
#include <simgear/nasal/cppbind/NasalCallContext.hxx>
|
||||
#include <simgear/nasal/cppbind/NasalHash.hxx>
|
||||
#include <simgear/sg_inlines.h>
|
||||
#include <simgear/structure/exception.hxx>
|
||||
|
||||
#include "addon_fwd.hxx"
|
||||
#include "AddonVersion.hxx"
|
||||
|
||||
using std::string;
|
||||
using std::vector;
|
||||
using simgear::enumValue;
|
||||
|
||||
namespace strutils = simgear::strutils;
|
||||
|
||||
namespace flightgear
|
||||
{
|
||||
|
||||
// ***************************************************************************
|
||||
// * AddonVersionSuffix *
|
||||
// ***************************************************************************
|
||||
|
||||
AddonVersionSuffix::AddonVersionSuffix(
|
||||
AddonVersionSuffixPrereleaseType preReleaseType, int preReleaseNum,
|
||||
bool developmental, int devNum)
|
||||
: _preReleaseType(preReleaseType),
|
||||
_preReleaseNum(preReleaseNum),
|
||||
_developmental(developmental),
|
||||
_devNum(devNum)
|
||||
{ }
|
||||
|
||||
// Construct an AddonVersionSuffix instance from a tuple (preReleaseType,
|
||||
// preReleaseNum, developmental, devNum). This would be nicer with
|
||||
// std::apply(), but it requires C++17.
|
||||
AddonVersionSuffix::AddonVersionSuffix(
|
||||
const std::tuple<AddonVersionSuffixPrereleaseType, int, bool, int>& t)
|
||||
: AddonVersionSuffix(std::get<0>(t), std::get<1>(t), std::get<2>(t),
|
||||
std::get<3>(t))
|
||||
{ }
|
||||
|
||||
AddonVersionSuffix::AddonVersionSuffix(const std::string& suffix)
|
||||
: AddonVersionSuffix(suffixStringToTuple(suffix))
|
||||
{ }
|
||||
|
||||
AddonVersionSuffix::AddonVersionSuffix(const char* suffix)
|
||||
: AddonVersionSuffix(string(suffix))
|
||||
{ }
|
||||
|
||||
// Static method
|
||||
string
|
||||
AddonVersionSuffix::releaseTypeStr(AddonVersionSuffixPrereleaseType releaseType)
|
||||
{
|
||||
switch (releaseType) {
|
||||
case AddonVersionSuffixPrereleaseType::alpha:
|
||||
return string("a");
|
||||
case AddonVersionSuffixPrereleaseType::beta:
|
||||
return string("b");
|
||||
case AddonVersionSuffixPrereleaseType::candidate:
|
||||
return string("rc");
|
||||
case AddonVersionSuffixPrereleaseType::none:
|
||||
return string();
|
||||
default:
|
||||
throw sg_error("unexpected value for member of "
|
||||
"flightgear::AddonVersionSuffixPrereleaseType: " +
|
||||
std::to_string(enumValue(releaseType)));
|
||||
}
|
||||
}
|
||||
|
||||
string
|
||||
AddonVersionSuffix::str() const
|
||||
{
|
||||
string res = releaseTypeStr(_preReleaseType);
|
||||
|
||||
if (!res.empty()) {
|
||||
res += std::to_string(_preReleaseNum);
|
||||
}
|
||||
|
||||
if (_developmental) {
|
||||
res += ".dev" + std::to_string(_devNum);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
// Static method
|
||||
std::tuple<AddonVersionSuffixPrereleaseType, int, bool, int>
|
||||
AddonVersionSuffix::suffixStringToTuple(const std::string& suffix)
|
||||
{
|
||||
// Use a simplified variant of the syntax described in PEP 440
|
||||
// <https://www.python.org/dev/peps/pep-0440/>: for the version suffix, only
|
||||
// allow a pre-release segment and a development release segment, but no
|
||||
// post-release segment.
|
||||
std::regex versionSuffixRegexp(R"((?:(a|b|rc)(\d+))?(?:\.dev(\d+))?)");
|
||||
std::smatch results;
|
||||
|
||||
if (std::regex_match(suffix, results, versionSuffixRegexp)) {
|
||||
const string preReleaseType_s = results.str(1);
|
||||
const string preReleaseNum_s = results.str(2);
|
||||
const string devNum_s = results.str(3);
|
||||
|
||||
AddonVersionSuffixPrereleaseType preReleaseType;
|
||||
int preReleaseNum = 0;
|
||||
int devNum = 0;
|
||||
|
||||
if (preReleaseType_s.empty()) {
|
||||
preReleaseType = AddonVersionSuffixPrereleaseType::none;
|
||||
} else {
|
||||
if (preReleaseType_s == "a") {
|
||||
preReleaseType = AddonVersionSuffixPrereleaseType::alpha;
|
||||
} else if (preReleaseType_s == "b") {
|
||||
preReleaseType = AddonVersionSuffixPrereleaseType::beta;
|
||||
} else if (preReleaseType_s == "rc") {
|
||||
preReleaseType = AddonVersionSuffixPrereleaseType::candidate;
|
||||
} else {
|
||||
assert(false); // the regexp should prevent this
|
||||
}
|
||||
|
||||
assert(!preReleaseNum_s.empty());
|
||||
preReleaseNum = strutils::readNonNegativeInt<int>(preReleaseNum_s);
|
||||
|
||||
if (preReleaseNum < 1) {
|
||||
string msg = "invalid add-on version suffix: '" + suffix + "' "
|
||||
"(prerelease number must be greater than or equal to 1, but got " +
|
||||
preReleaseNum_s + ")";
|
||||
throw sg_format_exception(msg, suffix);
|
||||
}
|
||||
}
|
||||
|
||||
if (!devNum_s.empty()) {
|
||||
devNum = strutils::readNonNegativeInt<int>(devNum_s);
|
||||
|
||||
if (devNum < 1) {
|
||||
string msg = "invalid add-on version suffix: '" + suffix + "' "
|
||||
"(development release number must be greater than or equal to 1, "
|
||||
"but got " + devNum_s + ")";
|
||||
throw sg_format_exception(msg, suffix);
|
||||
}
|
||||
}
|
||||
|
||||
return {preReleaseType, preReleaseNum, !devNum_s.empty(), devNum};
|
||||
} else { // the regexp didn't match
|
||||
string msg = "invalid add-on version suffix: '" + suffix + "' "
|
||||
"(expected form is [{a|b|rc}N1][.devN2] where N1 and N2 are positive "
|
||||
"integers)";
|
||||
throw sg_format_exception(msg, suffix);
|
||||
}
|
||||
}
|
||||
|
||||
// Beware, this is not suitable for sorting! cf. genSortKey() below.
|
||||
std::tuple<AddonVersionSuffixPrereleaseType, int, bool, int>
|
||||
AddonVersionSuffix::makeTuple() const
|
||||
{
|
||||
return { _preReleaseType, _preReleaseNum, _developmental, _devNum };
|
||||
}
|
||||
|
||||
std::tuple<int,
|
||||
std::underlying_type<AddonVersionSuffixPrereleaseType>::type,
|
||||
int, int, int>
|
||||
AddonVersionSuffix::genSortKey() const
|
||||
{
|
||||
using AddonRelType = AddonVersionSuffixPrereleaseType;
|
||||
|
||||
// The first element means that a plain .devN is lower than everything else,
|
||||
// except .devM with M <= N (namely: all dev and non-dev alpha, beta,
|
||||
// candidates, as well as the empty suffix).
|
||||
return { ((_developmental && _preReleaseType == AddonRelType::none) ? 0 : 1),
|
||||
enumValue(_preReleaseType),
|
||||
_preReleaseNum,
|
||||
(_developmental ? 0 : 1), // e.g., 1.0.3a2.devN < 1.0.3a2 for all N
|
||||
_devNum };
|
||||
}
|
||||
|
||||
bool operator==(const AddonVersionSuffix& lhs, const AddonVersionSuffix& rhs)
|
||||
{ return lhs.genSortKey() == rhs.genSortKey(); }
|
||||
|
||||
bool operator!=(const AddonVersionSuffix& lhs, const AddonVersionSuffix& rhs)
|
||||
{ return !operator==(lhs, rhs); }
|
||||
|
||||
bool operator< (const AddonVersionSuffix& lhs, const AddonVersionSuffix& rhs)
|
||||
{ return lhs.genSortKey() < rhs.genSortKey(); }
|
||||
|
||||
bool operator> (const AddonVersionSuffix& lhs, const AddonVersionSuffix& rhs)
|
||||
{ return operator<(rhs, lhs); }
|
||||
|
||||
bool operator<=(const AddonVersionSuffix& lhs, const AddonVersionSuffix& rhs)
|
||||
{ return !operator>(lhs, rhs); }
|
||||
|
||||
bool operator>=(const AddonVersionSuffix& lhs, const AddonVersionSuffix& rhs)
|
||||
{ return !operator<(lhs, rhs); }
|
||||
|
||||
std::ostream& operator<<(std::ostream& os,
|
||||
const AddonVersionSuffix& addonVersionSuffix)
|
||||
{ return os << addonVersionSuffix.str(); }
|
||||
|
||||
// ***************************************************************************
|
||||
// * AddonVersion *
|
||||
// ***************************************************************************
|
||||
|
||||
AddonVersion::AddonVersion(int major, int minor, int patchLevel,
|
||||
AddonVersionSuffix suffix)
|
||||
: _major(major),
|
||||
_minor(minor),
|
||||
_patchLevel(patchLevel),
|
||||
_suffix(std::move(suffix))
|
||||
{ }
|
||||
|
||||
// Construct an AddonVersion instance from a tuple (major, minor, patchLevel,
|
||||
// suffix). This would be nicer with std::apply(), but it requires C++17.
|
||||
AddonVersion::AddonVersion(
|
||||
const std::tuple<int, int, int, AddonVersionSuffix>& t)
|
||||
: AddonVersion(std::get<0>(t), std::get<1>(t), std::get<2>(t), std::get<3>(t))
|
||||
{ }
|
||||
|
||||
AddonVersion::AddonVersion(const std::string& versionStr)
|
||||
: AddonVersion(versionStringToTuple(versionStr))
|
||||
{ }
|
||||
|
||||
AddonVersion::AddonVersion(const char* versionStr)
|
||||
: AddonVersion(string(versionStr))
|
||||
{ }
|
||||
|
||||
// Static method
|
||||
std::tuple<int, int, int, AddonVersionSuffix>
|
||||
AddonVersion::versionStringToTuple(const std::string& versionStr)
|
||||
{
|
||||
// Use a simplified variant of the syntax described in PEP 440
|
||||
// <https://www.python.org/dev/peps/pep-0440/> (always 3 components in the
|
||||
// release segment, pre-release segment + development release segment; no
|
||||
// post-release segment allowed).
|
||||
std::regex versionRegexp(
|
||||
R"((\d+)\.(\d+).(\d+)(.*))");
|
||||
std::smatch results;
|
||||
|
||||
if (std::regex_match(versionStr, results, versionRegexp)) {
|
||||
const string majorNumber_s = results.str(1);
|
||||
const string minorNumber_s = results.str(2);
|
||||
const string patchLevel_s = results.str(3);
|
||||
const string suffix_s = results.str(4);
|
||||
|
||||
int major = strutils::readNonNegativeInt<int>(majorNumber_s);
|
||||
int minor = strutils::readNonNegativeInt<int>(minorNumber_s);
|
||||
int patchLevel = strutils::readNonNegativeInt<int>(patchLevel_s);
|
||||
|
||||
return {major, minor, patchLevel, AddonVersionSuffix(suffix_s)};
|
||||
} else { // the regexp didn't match
|
||||
string msg = "invalid add-on version number: '" + versionStr + "' "
|
||||
"(expected form is MAJOR.MINOR.PATCHLEVEL[{a|b|rc}N1][.devN2] where "
|
||||
"N1 and N2 are positive integers)";
|
||||
throw sg_format_exception(msg, versionStr);
|
||||
}
|
||||
}
|
||||
|
||||
int AddonVersion::majorNumber() const
|
||||
{ return _major; }
|
||||
|
||||
int AddonVersion::minorNumber() const
|
||||
{ return _minor; }
|
||||
|
||||
int AddonVersion::patchLevel() const
|
||||
{ return _patchLevel; }
|
||||
|
||||
AddonVersionSuffix AddonVersion::suffix() const
|
||||
{ return _suffix; }
|
||||
|
||||
std::string AddonVersion::suffixStr() const
|
||||
{ return suffix().str(); }
|
||||
|
||||
std::tuple<int, int, int, AddonVersionSuffix> AddonVersion::makeTuple() const
|
||||
{ return {majorNumber(), minorNumber(), patchLevel(), suffix()}; }
|
||||
|
||||
string AddonVersion::str() const
|
||||
{
|
||||
// Assemble the major.minor.patchLevel string
|
||||
vector<int> v({majorNumber(), minorNumber(), patchLevel()});
|
||||
string relSeg = std::accumulate(std::next(v.begin()), v.end(),
|
||||
std::to_string(v[0]),
|
||||
[](string s, int num) {
|
||||
return s + '.' + std::to_string(num);
|
||||
});
|
||||
|
||||
// Concatenate with the suffix string
|
||||
return relSeg + suffixStr();
|
||||
}
|
||||
|
||||
|
||||
bool operator==(const AddonVersion& lhs, const AddonVersion& rhs)
|
||||
{ return lhs.makeTuple() == rhs.makeTuple(); }
|
||||
|
||||
bool operator!=(const AddonVersion& lhs, const AddonVersion& rhs)
|
||||
{ return !operator==(lhs, rhs); }
|
||||
|
||||
bool operator< (const AddonVersion& lhs, const AddonVersion& rhs)
|
||||
{ return lhs.makeTuple() < rhs.makeTuple(); }
|
||||
|
||||
bool operator> (const AddonVersion& lhs, const AddonVersion& rhs)
|
||||
{ return operator<(rhs, lhs); }
|
||||
|
||||
bool operator<=(const AddonVersion& lhs, const AddonVersion& rhs)
|
||||
{ return !operator>(lhs, rhs); }
|
||||
|
||||
bool operator>=(const AddonVersion& lhs, const AddonVersion& rhs)
|
||||
{ return !operator<(lhs, rhs); }
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const AddonVersion& addonVersion)
|
||||
{ return os << addonVersion.str(); }
|
||||
|
||||
|
||||
// ***************************************************************************
|
||||
// * For the Nasal bindings *
|
||||
// ***************************************************************************
|
||||
|
||||
bool AddonVersion::equal(const nasal::CallContext& ctx) const
|
||||
{
|
||||
auto other = ctx.requireArg<AddonVersionRef>(0);
|
||||
return *this == *other;
|
||||
}
|
||||
|
||||
bool AddonVersion::nonEqual(const nasal::CallContext& ctx) const
|
||||
{
|
||||
auto other = ctx.requireArg<AddonVersionRef>(0);
|
||||
return *this != *other;
|
||||
}
|
||||
|
||||
bool AddonVersion::lowerThan(const nasal::CallContext& ctx) const
|
||||
{
|
||||
auto other = ctx.requireArg<AddonVersionRef>(0);
|
||||
return *this < *other;
|
||||
}
|
||||
|
||||
bool AddonVersion::lowerThanOrEqual(const nasal::CallContext& ctx) const
|
||||
{
|
||||
auto other = ctx.requireArg<AddonVersionRef>(0);
|
||||
return *this <= *other;
|
||||
}
|
||||
|
||||
bool AddonVersion::greaterThan(const nasal::CallContext& ctx) const
|
||||
{
|
||||
auto other = ctx.requireArg<AddonVersionRef>(0);
|
||||
return *this > *other;
|
||||
}
|
||||
|
||||
bool AddonVersion::greaterThanOrEqual(const nasal::CallContext& ctx) const
|
||||
{
|
||||
auto other = ctx.requireArg<AddonVersionRef>(0);
|
||||
return *this >= *other;
|
||||
}
|
||||
|
||||
// Static method
|
||||
void AddonVersion::setupGhost(nasal::Hash& addonsModule)
|
||||
{
|
||||
nasal::Ghost<AddonVersionRef>::init("addons.AddonVersion")
|
||||
.member("majorNumber", &AddonVersion::majorNumber)
|
||||
.member("minorNumber", &AddonVersion::minorNumber)
|
||||
.member("patchLevel", &AddonVersion::patchLevel)
|
||||
.member("suffix", &AddonVersion::suffixStr)
|
||||
.method("str", &AddonVersion::str)
|
||||
.method("equal", &AddonVersion::equal)
|
||||
.method("nonEqual", &AddonVersion::nonEqual)
|
||||
.method("lowerThan", &AddonVersion::lowerThan)
|
||||
.method("lowerThanOrEqual", &AddonVersion::lowerThanOrEqual)
|
||||
.method("greaterThan", &AddonVersion::greaterThan)
|
||||
.method("greaterThanOrEqual", &AddonVersion::greaterThanOrEqual);
|
||||
}
|
||||
|
||||
} // of namespace flightgear
|
175
src/Add-ons/AddonVersion.hxx
Normal file
175
src/Add-ons/AddonVersion.hxx
Normal file
|
@ -0,0 +1,175 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
//
|
||||
// AddonVersion.hxx --- Version class for FlightGear add-ons
|
||||
// Copyright (C) 2017 Florent Rougon
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#ifndef FG_ADDONVERSION_HXX
|
||||
#define FG_ADDONVERSION_HXX
|
||||
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
|
||||
#include <simgear/nasal/cppbind/NasalCallContext.hxx>
|
||||
#include <simgear/nasal/cppbind/NasalHash.hxx>
|
||||
#include <simgear/structure/SGReferenced.hxx>
|
||||
|
||||
#include "addon_fwd.hxx"
|
||||
|
||||
namespace flightgear
|
||||
{
|
||||
|
||||
// Order matters for the sorting/comparison functions
|
||||
enum class AddonVersionSuffixPrereleaseType {
|
||||
alpha = 0,
|
||||
beta,
|
||||
candidate,
|
||||
none
|
||||
};
|
||||
|
||||
// ***************************************************************************
|
||||
// * AddonVersionSuffix *
|
||||
// ***************************************************************************
|
||||
|
||||
class AddonVersionSuffix
|
||||
{
|
||||
public:
|
||||
AddonVersionSuffix(AddonVersionSuffixPrereleaseType _preReleaseType
|
||||
= AddonVersionSuffixPrereleaseType::none,
|
||||
int preReleaseNum = 0, bool developmental = false,
|
||||
int devNum = 0);
|
||||
// Construct from a string. The empty string is a valid input.
|
||||
AddonVersionSuffix(const std::string& suffix);
|
||||
AddonVersionSuffix(const char* suffix);
|
||||
// Construct from a tuple
|
||||
explicit AddonVersionSuffix(
|
||||
const std::tuple<AddonVersionSuffixPrereleaseType, int, bool, int>& t);
|
||||
|
||||
// Return all components of an AddonVersionSuffix instance as a tuple.
|
||||
// Beware, this is not suitable for sorting! cf. genSortKey() below.
|
||||
std::tuple<AddonVersionSuffixPrereleaseType, int, bool, int> makeTuple() const;
|
||||
|
||||
// String representation of an AddonVersionSuffix
|
||||
std::string str() const;
|
||||
|
||||
private:
|
||||
// Extract all components from a string representing a version suffix.
|
||||
// The components of the return value are, in this order:
|
||||
//
|
||||
// preReleaseType, preReleaseNum, developmental, devNum
|
||||
//
|
||||
// Note: the empty string is a valid input.
|
||||
static std::tuple<AddonVersionSuffixPrereleaseType, int, bool, int>
|
||||
suffixStringToTuple(const std::string& suffix);
|
||||
|
||||
// String representation of the release type component: "a", "b", "rc" or "".
|
||||
static std::string releaseTypeStr(AddonVersionSuffixPrereleaseType);
|
||||
// Useful for comparisons/sorting purposes
|
||||
std::tuple<int,
|
||||
std::underlying_type<AddonVersionSuffixPrereleaseType>::type,
|
||||
int, int, int> genSortKey() const;
|
||||
|
||||
friend bool operator==(const AddonVersionSuffix& lhs,
|
||||
const AddonVersionSuffix& rhs);
|
||||
friend bool operator<(const AddonVersionSuffix& lhs,
|
||||
const AddonVersionSuffix& rhs);
|
||||
|
||||
AddonVersionSuffixPrereleaseType _preReleaseType;
|
||||
int _preReleaseNum; // integer >= 1 (0 when not applicable)
|
||||
bool _developmental; // whether the suffix ends with '.devN'
|
||||
int _devNum; // integer >= 1 (0 when not applicable)
|
||||
};
|
||||
|
||||
|
||||
// operator==() and operator<() are declared above.
|
||||
bool operator!=(const AddonVersionSuffix& lhs, const AddonVersionSuffix& rhs);
|
||||
bool operator> (const AddonVersionSuffix& lhs, const AddonVersionSuffix& rhs);
|
||||
bool operator<=(const AddonVersionSuffix& lhs, const AddonVersionSuffix& rhs);
|
||||
bool operator>=(const AddonVersionSuffix& lhs, const AddonVersionSuffix& rhs);
|
||||
|
||||
std::ostream& operator<<(std::ostream&, const AddonVersionSuffix&);
|
||||
|
||||
// ***************************************************************************
|
||||
// * AddonVersion *
|
||||
// ***************************************************************************
|
||||
|
||||
// I suggest to use either the year-based FlightGear-type versioning, or
|
||||
// semantic versioning (<http://semver.org/>). For the suffix, we allow things
|
||||
// like "a1" (alpha1), "b2" (beta2), "rc4" (release candidate 4), "a1.dev3"
|
||||
// (development release for "a1", which sorts before "a1"), etc. It's a subset
|
||||
// of the syntax allowed in <https://www.python.org/dev/peps/pep-0440/>.
|
||||
class AddonVersion : public SGReferenced
|
||||
{
|
||||
public:
|
||||
AddonVersion(int major = 0, int minor = 0, int patchLevel = 0,
|
||||
AddonVersionSuffix suffix = AddonVersionSuffix());
|
||||
AddonVersion(const std::string& version);
|
||||
AddonVersion(const char* version);
|
||||
explicit AddonVersion(const std::tuple<int, int, int, AddonVersionSuffix>& t);
|
||||
|
||||
// Using the method names major() and minor() can lead to incomprehensible
|
||||
// errors such as "major is not a member of flightgear::AddonVersion"
|
||||
// because of a hideous glibc bug[1]: major() and minor() are defined by
|
||||
// standard headers as *macros*!
|
||||
//
|
||||
// [1] https://bugzilla.redhat.com/show_bug.cgi?id=130601
|
||||
int majorNumber() const;
|
||||
int minorNumber() const;
|
||||
int patchLevel() const;
|
||||
AddonVersionSuffix suffix() const;
|
||||
std::string suffixStr() const;
|
||||
|
||||
std::string str() const;
|
||||
|
||||
// For the Nasal bindings (otherwise, we have operator==(), etc.)
|
||||
bool equal(const nasal::CallContext& ctx) const;
|
||||
bool nonEqual(const nasal::CallContext& ctx) const;
|
||||
bool lowerThan(const nasal::CallContext& ctx) const;
|
||||
bool lowerThanOrEqual(const nasal::CallContext& ctx) const;
|
||||
bool greaterThan(const nasal::CallContext& ctx) const;
|
||||
bool greaterThanOrEqual(const nasal::CallContext& ctx) const;
|
||||
|
||||
static void setupGhost(nasal::Hash& addonsModule);
|
||||
|
||||
private:
|
||||
// Useful for comparisons/sorting purposes
|
||||
std::tuple<int, int, int, AddonVersionSuffix> makeTuple() const;
|
||||
|
||||
static std::tuple<int, int, int, AddonVersionSuffix>
|
||||
versionStringToTuple(const std::string& versionStr);
|
||||
|
||||
friend bool operator==(const AddonVersion& lhs, const AddonVersion& rhs);
|
||||
friend bool operator<(const AddonVersion& lhs, const AddonVersion& rhs);
|
||||
|
||||
int _major;
|
||||
int _minor;
|
||||
int _patchLevel;
|
||||
AddonVersionSuffix _suffix;
|
||||
};
|
||||
|
||||
// operator==() and operator<() are declared above.
|
||||
bool operator!=(const AddonVersion& lhs, const AddonVersion& rhs);
|
||||
bool operator> (const AddonVersion& lhs, const AddonVersion& rhs);
|
||||
bool operator<=(const AddonVersion& lhs, const AddonVersion& rhs);
|
||||
bool operator>=(const AddonVersion& lhs, const AddonVersion& rhs);
|
||||
|
||||
std::ostream& operator<<(std::ostream&, const AddonVersion&);
|
||||
|
||||
} // of namespace flightgear
|
||||
|
||||
#endif // of FG_ADDONVERSION_HXX
|
10
src/Add-ons/CMakeLists.txt
Normal file
10
src/Add-ons/CMakeLists.txt
Normal file
|
@ -0,0 +1,10 @@
|
|||
include(FlightGearComponent)
|
||||
|
||||
set(SOURCES Addon.cxx AddonManager.cxx AddonVersion.cxx)
|
||||
set(HEADERS addon_fwd.hxx Addon.hxx AddonManager.hxx AddonVersion.hxx)
|
||||
|
||||
flightgear_component(AddonManagement "${SOURCES}" "${HEADERS}")
|
||||
|
||||
if (COMMAND flightgear_test)
|
||||
flightgear_test(test_AddonManagement test_AddonManagement.cxx)
|
||||
endif()
|
52
src/Add-ons/addon_fwd.hxx
Normal file
52
src/Add-ons/addon_fwd.hxx
Normal file
|
@ -0,0 +1,52 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
//
|
||||
// addon_fwd.hxx --- Forward declarations for the FlightGear add-on
|
||||
// infrastructure
|
||||
// Copyright (C) 2017 Florent Rougon
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#ifndef FG_ADDON_FWD_HXX
|
||||
#define FG_ADDON_FWD_HXX
|
||||
|
||||
#include <simgear/structure/SGSharedPtr.hxx>
|
||||
|
||||
namespace flightgear
|
||||
{
|
||||
|
||||
class Addon;
|
||||
class AddonManager;
|
||||
class AddonVersion;
|
||||
class AddonVersionSuffix;
|
||||
|
||||
using AddonRef = SGSharedPtr<Addon>;
|
||||
using AddonVersionRef = SGSharedPtr<AddonVersion>;
|
||||
|
||||
namespace addon_errors
|
||||
{
|
||||
|
||||
class error;
|
||||
class error_loading_config_file;
|
||||
class no_metadata_file_found;
|
||||
class error_loading_metadata_file;
|
||||
class duplicate_registration_attempt;
|
||||
class fg_version_too_old;
|
||||
class fg_version_too_recent;
|
||||
|
||||
} // of namespace addon_errors
|
||||
|
||||
} // of namespace flightgear
|
||||
|
||||
#endif // of FG_ADDON_FWD_HXX
|
249
src/Add-ons/test_AddonManagement.cxx
Normal file
249
src/Add-ons/test_AddonManagement.cxx
Normal file
|
@ -0,0 +1,249 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
//
|
||||
// test_AddonManagement.cxx --- Automated tests for FlightGear classes dealing
|
||||
// with add-ons
|
||||
// Copyright (C) 2017 Florent Rougon
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#include "config.h"
|
||||
#include "unitTestHelpers.hxx"
|
||||
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
|
||||
#include <simgear/misc/test_macros.hxx>
|
||||
|
||||
#include "Add-ons/Addon.hxx"
|
||||
#include "Add-ons/AddonVersion.hxx"
|
||||
|
||||
using std::string;
|
||||
using std::vector;
|
||||
|
||||
using namespace flightgear;
|
||||
|
||||
|
||||
void testAddonVersionSuffix()
|
||||
{
|
||||
using AddonRelType = AddonVersionSuffixPrereleaseType;
|
||||
|
||||
fgtest::initTestGlobals("AddonVersion");
|
||||
|
||||
AddonVersionSuffix v1(AddonRelType::beta, 2, true, 5);
|
||||
AddonVersionSuffix v1Copy(v1);
|
||||
AddonVersionSuffix v1NonDev(AddonRelType::beta, 2, false);
|
||||
SG_CHECK_EQUAL(v1, v1Copy);
|
||||
SG_CHECK_EQUAL(v1, AddonVersionSuffix("b2.dev5"));
|
||||
SG_CHECK_EQUAL_NOSTREAM(v1.makeTuple(),
|
||||
std::make_tuple(AddonRelType::beta, 2, true, 5));
|
||||
SG_CHECK_EQUAL(AddonVersionSuffix(), AddonVersionSuffix(""));
|
||||
// A simple comparison
|
||||
SG_CHECK_LT(v1, v1NonDev); // b2.dev5 < b2
|
||||
|
||||
// Check string representation with str()
|
||||
SG_CHECK_EQUAL(AddonVersionSuffix(AddonRelType::none).str(), "");
|
||||
SG_CHECK_EQUAL(AddonVersionSuffix(AddonRelType::none, 0, true, 12).str(),
|
||||
".dev12");
|
||||
SG_CHECK_EQUAL(AddonVersionSuffix(AddonRelType::alpha, 1).str(), "a1");
|
||||
SG_CHECK_EQUAL(AddonVersionSuffix(AddonRelType::alpha, 1, false).str(), "a1");
|
||||
SG_CHECK_EQUAL(AddonVersionSuffix(AddonRelType::alpha, 2, true, 4).str(),
|
||||
"a2.dev4");
|
||||
|
||||
SG_CHECK_EQUAL(AddonVersionSuffix(AddonRelType::beta, 1).str(), "b1");
|
||||
SG_CHECK_EQUAL(AddonVersionSuffix(AddonRelType::beta, 1, false).str(), "b1");
|
||||
SG_CHECK_EQUAL(AddonVersionSuffix(AddonRelType::beta, 2, true, 4).str(),
|
||||
"b2.dev4");
|
||||
|
||||
SG_CHECK_EQUAL(AddonVersionSuffix(AddonRelType::candidate, 1).str(), "rc1");
|
||||
SG_CHECK_EQUAL(AddonVersionSuffix(AddonRelType::candidate, 1, false).str(),
|
||||
"rc1");
|
||||
SG_CHECK_EQUAL(AddonVersionSuffix(AddonRelType::candidate, 2, true, 4).str(),
|
||||
"rc2.dev4");
|
||||
|
||||
// Check stream output
|
||||
std::ostringstream oss;
|
||||
oss << AddonVersionSuffix(AddonRelType::candidate, 2, true, 4);
|
||||
SG_CHECK_EQUAL(oss.str(), "rc2.dev4");
|
||||
|
||||
// Check ordering with all types of transitions, using operator<()
|
||||
auto checkStrictOrdering = [](const vector<AddonVersionSuffix>& v) {
|
||||
assert(v.size() > 1);
|
||||
for (std::size_t i=0; i < v.size() - 1; i++) {
|
||||
SG_CHECK_LT(v[i], v[i+1]);
|
||||
}
|
||||
};
|
||||
|
||||
checkStrictOrdering({
|
||||
{AddonRelType::none, 0, true, 1},
|
||||
{AddonRelType::none, 0, true, 2},
|
||||
{AddonRelType::alpha, 1, true, 1},
|
||||
{AddonRelType::alpha, 1, true, 2},
|
||||
{AddonRelType::alpha, 1, true, 3},
|
||||
{AddonRelType::alpha, 1, false},
|
||||
{AddonRelType::alpha, 2, true, 1},
|
||||
{AddonRelType::alpha, 2, true, 3},
|
||||
{AddonRelType::alpha, 2, false},
|
||||
{AddonRelType::beta, 1, true, 1},
|
||||
{AddonRelType::beta, 1, true, 25},
|
||||
{AddonRelType::beta, 1, false},
|
||||
{AddonRelType::beta, 2, true, 1},
|
||||
{AddonRelType::beta, 2, true, 2},
|
||||
{AddonRelType::beta, 2, false},
|
||||
{AddonRelType::candidate, 1, true, 1},
|
||||
{AddonRelType::candidate, 1, true, 2},
|
||||
{AddonRelType::candidate, 1, false},
|
||||
{AddonRelType::candidate, 2, true, 1},
|
||||
{AddonRelType::candidate, 2, true, 2},
|
||||
{AddonRelType::candidate, 2, false},
|
||||
{AddonRelType::candidate, 21, false},
|
||||
{AddonRelType::none}
|
||||
});
|
||||
|
||||
// Check operator>() and operator!=()
|
||||
SG_CHECK_GT(AddonVersionSuffix(AddonRelType::none),
|
||||
AddonVersionSuffix(AddonRelType::candidate, 21, false));
|
||||
|
||||
SG_CHECK_NE(AddonVersionSuffix(AddonRelType::none),
|
||||
AddonVersionSuffix(AddonRelType::candidate, 21, false));
|
||||
|
||||
// Check operator<=() and operator>=()
|
||||
SG_CHECK_LE(AddonVersionSuffix(AddonRelType::candidate, 2, false),
|
||||
AddonVersionSuffix(AddonRelType::candidate, 2, false));
|
||||
SG_CHECK_LE(AddonVersionSuffix(AddonRelType::candidate, 2, false),
|
||||
AddonVersionSuffix(AddonRelType::none));
|
||||
|
||||
SG_CHECK_GE(AddonVersionSuffix(AddonRelType::none),
|
||||
AddonVersionSuffix(AddonRelType::none));
|
||||
SG_CHECK_GE(AddonVersionSuffix(AddonRelType::none),
|
||||
AddonVersionSuffix(AddonRelType::candidate, 21, false));
|
||||
|
||||
fgtest::shutdownTestGlobals();
|
||||
}
|
||||
|
||||
void testAddonVersion()
|
||||
{
|
||||
using AddonRelType = AddonVersionSuffixPrereleaseType;
|
||||
|
||||
fgtest::initTestGlobals("AddonVersion");
|
||||
|
||||
AddonVersionSuffix suffix(AddonRelType::beta, 2, true, 5);
|
||||
AddonVersion v1(2017, 4, 7, suffix);
|
||||
AddonVersion v1Copy(v1);
|
||||
AddonVersion v2 = v1;
|
||||
AddonVersion v3(std::move(v1Copy));
|
||||
AddonVersion v4 = std::move(v2);
|
||||
|
||||
SG_CHECK_EQUAL(v1, AddonVersion("2017.4.7b2.dev5"));
|
||||
SG_CHECK_EQUAL(v1, AddonVersion(std::make_tuple(2017, 4, 7, suffix)));
|
||||
SG_CHECK_EQUAL(v1, v3);
|
||||
SG_CHECK_EQUAL(v1, v4);
|
||||
SG_CHECK_LT(v1, AddonVersion("2017.4.7b2"));
|
||||
SG_CHECK_LE(v1, AddonVersion("2017.4.7b2"));
|
||||
SG_CHECK_LE(v1, v1);
|
||||
SG_CHECK_GT(AddonVersion("2017.4.7b2"), v1);
|
||||
SG_CHECK_GE(AddonVersion("2017.4.7b2"), v1);
|
||||
SG_CHECK_GE(v1, v1);
|
||||
SG_CHECK_NE(v1, AddonVersion("2017.4.7b3"));
|
||||
|
||||
SG_CHECK_EQUAL(v1.majorNumber(), 2017);
|
||||
SG_CHECK_EQUAL(v1.minorNumber(), 4);
|
||||
SG_CHECK_EQUAL(v1.patchLevel(), 7);
|
||||
SG_CHECK_EQUAL(v1.suffix(), suffix);
|
||||
|
||||
// Round-trips std::string <-> AddonVersion
|
||||
SG_CHECK_EQUAL(AddonVersion("2017.4.7.dev13").str(), "2017.4.7.dev13");
|
||||
SG_CHECK_EQUAL(AddonVersion("2017.4.7a2.dev8").str(), "2017.4.7a2.dev8");
|
||||
SG_CHECK_EQUAL(AddonVersion("2017.4.7a2").str(), "2017.4.7a2");
|
||||
SG_CHECK_EQUAL(AddonVersion("2017.4.7b2.dev5").str(), "2017.4.7b2.dev5");
|
||||
SG_CHECK_EQUAL(AddonVersion("2017.4.7b2").str(), "2017.4.7b2");
|
||||
SG_CHECK_EQUAL(AddonVersion("2017.4.7rc1.dev3").str(), "2017.4.7rc1.dev3");
|
||||
SG_CHECK_EQUAL(AddonVersion("2017.4.7rc1").str(), "2017.4.7rc1");
|
||||
SG_CHECK_EQUAL(AddonVersion("2017.4.7").str(), "2017.4.7");
|
||||
|
||||
// Check stream output
|
||||
std::ostringstream oss;
|
||||
oss << AddonVersion("2017.4.7b2.dev5");
|
||||
SG_CHECK_EQUAL(oss.str(), "2017.4.7b2.dev5");
|
||||
|
||||
// Check ordering with all types of transitions, using operator<()
|
||||
auto checkStrictOrdering = [](const vector<AddonVersion>& v) {
|
||||
assert(v.size() > 1);
|
||||
for (std::size_t i=0; i < v.size() - 1; i++) {
|
||||
SG_CHECK_LT(v[i], v[i+1]);
|
||||
}
|
||||
};
|
||||
|
||||
checkStrictOrdering({
|
||||
"3.12.8.dev1", "3.12.8.dev2", "3.12.8.dev12", "3.12.8a1.dev1",
|
||||
"3.12.8a1.dev2", "3.12.8a1", "3.12.8a2", "3.12.8b1.dev1",
|
||||
"3.12.8b1.dev2", "3.12.8b1", "3.12.8b2", "3.12.8b10",
|
||||
"3.12.8rc1.dev1", "3.12.8rc1.dev2", "3.12.8rc1.dev3",
|
||||
"3.12.8rc1", "3.12.8rc2", "3.12.8rc3", "3.12.8", "3.12.9.dev1",
|
||||
"3.12.9", "3.13.0", "4.0.0.dev1", "4.0.0.dev10", "4.0.0a1", "4.0.0",
|
||||
"2017.4.0", "2017.4.1", "2017.4.10", "2017.5.0", "2018.0.0"});
|
||||
|
||||
fgtest::shutdownTestGlobals();
|
||||
}
|
||||
|
||||
void testAddon()
|
||||
{
|
||||
Addon m;
|
||||
std::string addonId = "org.FlightGear.addons.MyGreatAddon";
|
||||
m.setId(addonId);
|
||||
m.setVersion(AddonVersion("2017.2.5rc3"));
|
||||
m.setBasePath(SGPath("/path/to/MyGreatAddon"));
|
||||
m.setMinFGVersionRequired("2017.4.1");
|
||||
m.setMaxFGVersionRequired("none");
|
||||
|
||||
SG_CHECK_EQUAL(m.getId(), addonId);
|
||||
SG_CHECK_EQUAL(*m.getVersion(), AddonVersion("2017.2.5rc3"));
|
||||
SG_CHECK_EQUAL(m.getBasePath(), SGPath("/path/to/MyGreatAddon"));
|
||||
SG_CHECK_EQUAL(m.getMinFGVersionRequired(), "2017.4.1");
|
||||
|
||||
const string refText = "addon '" + addonId + "' (version = 2017.2.5rc3, "
|
||||
"base path = '/path/to/MyGreatAddon', "
|
||||
"minFGVersionRequired = '2017.4.1', "
|
||||
"maxFGVersionRequired = 'none')";
|
||||
SG_CHECK_EQUAL(m.str(), refText);
|
||||
|
||||
// Check stream output
|
||||
std::ostringstream oss;
|
||||
oss << m;
|
||||
SG_CHECK_EQUAL(oss.str(), refText);
|
||||
|
||||
// Set a max FG version and recheck
|
||||
m.setMaxFGVersionRequired("2018.2.5");
|
||||
const string refText2 = "addon '" + addonId + "' (version = 2017.2.5rc3, "
|
||||
"base path = '/path/to/MyGreatAddon', "
|
||||
"minFGVersionRequired = '2017.4.1', "
|
||||
"maxFGVersionRequired = '2018.2.5')";
|
||||
SG_CHECK_EQUAL(m.getMaxFGVersionRequired(), "2018.2.5");
|
||||
SG_CHECK_EQUAL(m.str(), refText2);
|
||||
}
|
||||
|
||||
int main(int argc, const char* const* argv)
|
||||
{
|
||||
testAddonVersionSuffix();
|
||||
testAddonVersion();
|
||||
testAddon();
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
|
@ -4,6 +4,7 @@ include_directories(${PROJECT_SOURCE_DIR})
|
|||
# note order here affects link order, and hence linking correctness
|
||||
# on systems with a traditional ld (eg, GNU ld on Linux)
|
||||
foreach( mylibfolder
|
||||
Add-ons
|
||||
Airports
|
||||
Aircraft
|
||||
ATC
|
||||
|
|
|
@ -76,6 +76,8 @@
|
|||
#include <simgear/package/Install.hxx>
|
||||
#include <simgear/package/Catalog.hxx>
|
||||
|
||||
#include <Add-ons/AddonManager.hxx>
|
||||
|
||||
#include <Aircraft/controls.hxx>
|
||||
#include <Aircraft/replay.hxx>
|
||||
#include <Aircraft/FlightHistory.hxx>
|
||||
|
@ -1136,6 +1138,9 @@ void fgStartNewReset()
|
|||
// otherwise channels are duplicated
|
||||
globals->get_channel_options_list()->clear();
|
||||
|
||||
flightgear::AddonManager::reset();
|
||||
flightgear::AddonManager::createInstance();
|
||||
|
||||
fgInitConfig(0, NULL, true);
|
||||
fgInitGeneral(); // all of this?
|
||||
|
||||
|
|
|
@ -56,6 +56,7 @@ extern bool global_crashRptEnabled;
|
|||
#include <simgear/math/sg_random.h>
|
||||
#include <simgear/misc/strutils.hxx>
|
||||
|
||||
#include <Add-ons/AddonManager.hxx>
|
||||
#include <Main/locale.hxx>
|
||||
#include <Model/panelnode.hxx>
|
||||
#include <Scenery/scenery.hxx>
|
||||
|
@ -534,6 +535,8 @@ int fgMainInit( int argc, char **argv )
|
|||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
AddonManager::createInstance();
|
||||
|
||||
configResult = flightgear::Options::sharedInstance()->processOptions();
|
||||
if (configResult == flightgear::FG_OPTIONS_ERROR) {
|
||||
return EXIT_FAILURE;
|
||||
|
|
|
@ -65,6 +65,7 @@
|
|||
#include <GUI/SetupRootDialog.hxx>
|
||||
#endif
|
||||
|
||||
#include <Add-ons/AddonManager.hxx>
|
||||
#include <Main/locale.hxx>
|
||||
#include <Navaids/NavDataCache.hxx>
|
||||
#include "globals.hxx"
|
||||
|
@ -705,33 +706,16 @@ clearLocation ()
|
|||
static int
|
||||
fgOptAddon(const char *arg)
|
||||
{
|
||||
const SGPath path(SGPath::fromLocal8Bit(arg));
|
||||
const SGPath config_xml = path / "config.xml";
|
||||
const SGPath addonPath = SGPath::fromLocal8Bit(arg);
|
||||
const auto& addonManager = AddonManager::instance();
|
||||
|
||||
if (config_xml.exists()) {
|
||||
try {
|
||||
readProperties(config_xml, globals->get_props());
|
||||
addonManager->registerAddon(addonPath);
|
||||
} catch (const sg_exception &e) {
|
||||
const string msg = "Unable to load '" + config_xml.utf8Str() + "'. "
|
||||
"Please check that this file exists and is readable.\n\n" +
|
||||
"Exception information: " + e.getFormattedMessage();
|
||||
string msg = "Error registering an add-on: " + e.getFormattedMessage();
|
||||
SG_LOG(SG_GENERAL, SG_ALERT, msg);
|
||||
flightgear::fatalMessageBoxThenExit(
|
||||
"FlightGear", "Unable to load an addon's config.xml file.", msg);
|
||||
}
|
||||
|
||||
globals->append_aircraft_path(path);
|
||||
fgGetNode("addons", true)
|
||||
->addChild("addon")
|
||||
->getNode("path", true)
|
||||
->setStringValue(path.utf8Str());
|
||||
} else {
|
||||
flightgear::fatalMessageBoxThenExit(
|
||||
"FlightGear",
|
||||
"Path specified with --addon does not exist or no config.xml found in "
|
||||
"that path.",
|
||||
"Unable to find the file '" + config_xml.utf8Str() + "'."
|
||||
);
|
||||
"FlightGear", "Unable to register an add-on.", msg);
|
||||
}
|
||||
|
||||
return FG_OPTIONS_OK;
|
||||
|
|
|
@ -3,6 +3,7 @@ include(FlightGearComponent)
|
|||
set(SOURCES
|
||||
NasalSys.cxx
|
||||
nasal-props.cxx
|
||||
NasalAddons.cxx
|
||||
NasalAircraft.cxx
|
||||
NasalPositioned.cxx
|
||||
NasalPositioned_cppbind.cxx
|
||||
|
@ -18,6 +19,7 @@ set(SOURCES
|
|||
set(HEADERS
|
||||
NasalSys.hxx
|
||||
NasalSys_private.hxx
|
||||
NasalAddons.hxx
|
||||
NasalAircraft.hxx
|
||||
NasalPositioned.hxx
|
||||
NasalCanvas.hxx
|
||||
|
|
197
src/Scripting/NasalAddons.cxx
Normal file
197
src/Scripting/NasalAddons.cxx
Normal file
|
@ -0,0 +1,197 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
//
|
||||
// NasalAddons.cxx --- Expose add-on classes to Nasal
|
||||
// Copyright (C) 2017 Florent Rougon
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include <simgear/nasal/cppbind/Ghost.hxx>
|
||||
#include <simgear/nasal/cppbind/NasalCallContext.hxx>
|
||||
#include <simgear/nasal/cppbind/NasalHash.hxx>
|
||||
#include <simgear/nasal/nasal.h>
|
||||
#include <simgear/structure/exception.hxx>
|
||||
|
||||
#include <Add-ons/addon_fwd.hxx>
|
||||
#include <Add-ons/Addon.hxx>
|
||||
#include <Add-ons/AddonManager.hxx>
|
||||
#include <Add-ons/AddonVersion.hxx>
|
||||
|
||||
namespace flightgear
|
||||
{
|
||||
|
||||
// ***************************************************************************
|
||||
// * AddonManager *
|
||||
// ***************************************************************************
|
||||
|
||||
static const std::unique_ptr<AddonManager>& getAddonMgrWithCheck()
|
||||
{
|
||||
const auto& addonMgr = AddonManager::instance();
|
||||
|
||||
if (!addonMgr) {
|
||||
throw sg_exception("trying to access the AddonManager from Nasal, "
|
||||
"however it is not initialized");
|
||||
}
|
||||
|
||||
return addonMgr;
|
||||
}
|
||||
|
||||
static naRef f_registeredAddons(const nasal::CallContext& ctx)
|
||||
{
|
||||
const auto& addonMgr = getAddonMgrWithCheck();
|
||||
return ctx.to_nasal(addonMgr->registeredAddons());
|
||||
}
|
||||
|
||||
static naRef f_isAddonRegistered(const nasal::CallContext& ctx)
|
||||
{
|
||||
const auto& addonMgr = getAddonMgrWithCheck();
|
||||
std::string addonId = ctx.requireArg<std::string>(0);
|
||||
return ctx.to_nasal(addonMgr->isAddonRegistered(addonId));
|
||||
}
|
||||
|
||||
static naRef f_loadedAddons(const nasal::CallContext& ctx)
|
||||
{
|
||||
const auto& addonMgr = getAddonMgrWithCheck();
|
||||
return ctx.to_nasal(addonMgr->loadedAddons());
|
||||
}
|
||||
|
||||
static naRef f_isAddonLoaded(const nasal::CallContext& ctx)
|
||||
{
|
||||
const auto& addonMgr = getAddonMgrWithCheck();
|
||||
std::string addonId = ctx.requireArg<std::string>(0);
|
||||
return ctx.to_nasal(addonMgr->isAddonLoaded(addonId));
|
||||
}
|
||||
|
||||
static naRef f_getAddon(const nasal::CallContext& ctx)
|
||||
{
|
||||
const auto& addonMgr = getAddonMgrWithCheck();
|
||||
return ctx.to_nasal(addonMgr->getAddon(ctx.requireArg<std::string>(0)));
|
||||
}
|
||||
|
||||
static void wrapAddonManagerMethods(nasal::Hash& addonsModule)
|
||||
{
|
||||
addonsModule.set("registeredAddons", &f_registeredAddons);
|
||||
addonsModule.set("isAddonRegistered", &f_isAddonRegistered);
|
||||
addonsModule.set("loadedAddons", &f_loadedAddons);
|
||||
addonsModule.set("isAddonLoaded", &f_isAddonLoaded);
|
||||
addonsModule.set("getAddon", &f_getAddon);
|
||||
}
|
||||
|
||||
// ***************************************************************************
|
||||
// * AddonVersion *
|
||||
// ***************************************************************************
|
||||
|
||||
// Create a new AddonVersion instance.
|
||||
//
|
||||
// addons.AddonVersion.new(versionString)
|
||||
// addons.AddonVersion.new(major[, minor[, patchLevel[, suffix]]])
|
||||
//
|
||||
// where:
|
||||
// - 'major', 'minor' and 'patchLevel' are integers;
|
||||
// - 'suffix' is a string such as "", "a3", "b12" or "rc4" (resp. meaning:
|
||||
// release, alpha 3, beta 12, release candidate 4) Suffixes for
|
||||
// development releases are also allowed, such as ".dev4" (sorts before
|
||||
// the release as well as any alphas, betas and rcs for the release) and
|
||||
// "a3.dev10" (sorts before "a3.dev11", "a3.dev12", "a3", etc.).
|
||||
//
|
||||
// For details, see <https://www.python.org/dev/peps/pep-0440/> which is a
|
||||
// proper superset of what is allowed here.
|
||||
naRef f_createAddonVersion(const nasal::CallContext& ctx)
|
||||
{
|
||||
int major = 0, minor = 0, patchLevel = 0;
|
||||
std::string suffix;
|
||||
|
||||
if (ctx.argc == 0 || ctx.argc > 4) {
|
||||
naRuntimeError(ctx.c,
|
||||
"AddonVersion.new(versionString) or "
|
||||
"AddonVersion.new(major[, minor[, patchLevel[, suffix]]])");
|
||||
}
|
||||
|
||||
if (ctx.argc == 1) {
|
||||
naRef arg1 = ctx.args[0];
|
||||
|
||||
if (naIsString(arg1)) {
|
||||
return ctx.to_nasal(AddonVersionRef(new AddonVersion(naStr_data(arg1))));
|
||||
} else if (naIsNum(arg1)) {
|
||||
AddonVersionRef ref{new AddonVersion(arg1.num, minor, patchLevel,
|
||||
AddonVersionSuffix(suffix))};
|
||||
return ctx.to_nasal(std::move(ref));
|
||||
} else {
|
||||
naRuntimeError(ctx.c,
|
||||
"AddonVersion.new(versionString) or "
|
||||
"AddonVersion.new(major[, minor[, patchLevel[, suffix]]])");
|
||||
}
|
||||
}
|
||||
|
||||
assert(ctx.argc > 0);
|
||||
if (!naIsNum(ctx.args[0])) {
|
||||
naRuntimeError(
|
||||
ctx.c, "addons.AddonVersion.new() requires major number as an integer");
|
||||
}
|
||||
|
||||
major = ctx.args[0].num;
|
||||
|
||||
if (ctx.argc > 1) {
|
||||
if (!naIsNum(ctx.args[1])) {
|
||||
naRuntimeError(
|
||||
ctx.c, "addons.AddonVersion.new() requires minor number as an integer");
|
||||
}
|
||||
|
||||
minor = ctx.args[1].num;
|
||||
}
|
||||
|
||||
if (ctx.argc > 2) {
|
||||
if (!naIsNum(ctx.args[2])) {
|
||||
naRuntimeError(
|
||||
ctx.c, "addons.AddonVersion.new() requires patch level as an integer");
|
||||
}
|
||||
|
||||
patchLevel = ctx.args[2].num;
|
||||
}
|
||||
|
||||
if (ctx.argc > 3) {
|
||||
if (!naIsString(ctx.args[3])) {
|
||||
naRuntimeError(
|
||||
ctx.c, "addons.AddonVersion.new() requires suffix as a string");
|
||||
}
|
||||
|
||||
suffix = naStr_data(ctx.args[3]);
|
||||
}
|
||||
|
||||
assert(ctx.argc <= 4);
|
||||
|
||||
return ctx.to_nasal(
|
||||
AddonVersionRef(new AddonVersion(major, minor, patchLevel,
|
||||
AddonVersionSuffix(suffix))));
|
||||
}
|
||||
|
||||
void initAddonClassesForNasal(naRef globals, naContext c)
|
||||
{
|
||||
nasal::Hash globalsModule(globals, c);
|
||||
nasal::Hash addonsModule = globalsModule.createHash("addons");
|
||||
|
||||
wrapAddonManagerMethods(addonsModule);
|
||||
|
||||
Addon::setupGhost(addonsModule);
|
||||
|
||||
AddonVersion::setupGhost(addonsModule);
|
||||
addonsModule.createHash("AddonVersion").set("new", &f_createAddonVersion);
|
||||
}
|
||||
|
||||
} // of namespace flightgear
|
32
src/Scripting/NasalAddons.hxx
Normal file
32
src/Scripting/NasalAddons.hxx
Normal file
|
@ -0,0 +1,32 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
//
|
||||
// NasalAddons.hxx --- Expose add-on classes to Nasal
|
||||
// Copyright (C) 2017 Florent Rougon
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#ifndef FG_ADDON_NASAL_INTERFACE_HXX
|
||||
#define FG_ADDON_NASAL_INTERFACE_HXX
|
||||
|
||||
#include <simgear/nasal/nasal.h>
|
||||
|
||||
namespace flightgear
|
||||
{
|
||||
|
||||
void initAddonClassesForNasal(naRef globals, naContext c);
|
||||
|
||||
} // of namespace flightgear
|
||||
|
||||
#endif // of FG_ADDON_NASAL_INTERFACE_HXX
|
|
@ -38,6 +38,7 @@
|
|||
#include <simgear/nasal/cppbind/Ghost.hxx>
|
||||
#include <simgear/nasal/cppbind/NasalHash.hxx>
|
||||
|
||||
#include "NasalAddons.hxx"
|
||||
#include "NasalSGPath.hxx"
|
||||
#include "NasalSys.hxx"
|
||||
#include "NasalSys_private.hxx"
|
||||
|
@ -856,6 +857,8 @@ void FGNasalSys::setCmdArg(SGPropertyNode* aNode)
|
|||
|
||||
void FGNasalSys::init()
|
||||
{
|
||||
using namespace flightgear;
|
||||
|
||||
if (_inited) {
|
||||
SG_LOG(SG_GENERAL, SG_ALERT, "duplicate init of Nasal");
|
||||
}
|
||||
|
@ -881,7 +884,7 @@ void FGNasalSys::init()
|
|||
for(i=0; funcs[i].name; i++)
|
||||
hashset(_globals, funcs[i].name,
|
||||
naNewFunc(_context, naNewCCode(_context, funcs[i].func)));
|
||||
nasal::Hash io_module = nasal::Hash(_globals, _context).get<nasal::Hash>("io");
|
||||
nasal::Hash io_module = getGlobals().get<nasal::Hash>("io");
|
||||
io_module.set("open", f_open);
|
||||
|
||||
// And our SGPropertyNode wrapper
|
||||
|
@ -911,6 +914,8 @@ void FGNasalSys::init()
|
|||
.member("simulatedTime", &TimerObj::isSimTime, &f_timerObj_setSimTime)
|
||||
.member("isRunning", &TimerObj::isRunning);
|
||||
|
||||
initAddonClassesForNasal(_globals, _context);
|
||||
|
||||
// Now load the various source files in the Nasal directory
|
||||
simgear::Dir nasalDir(SGPath(globals->get_fg_root(), "Nasal"));
|
||||
loadScriptDirectory(nasalDir);
|
||||
|
|
|
@ -7,6 +7,9 @@ set(sources
|
|||
Main/locale.cxx
|
||||
Main/util.cxx
|
||||
Main/positioninit.cxx
|
||||
Add-ons/Addon.cxx
|
||||
Add-ons/AddonManager.cxx
|
||||
Add-ons/AddonVersion.cxx
|
||||
Aircraft/controls.cxx
|
||||
Aircraft/FlightHistory.cxx
|
||||
Aircraft/flightrecorder.cxx
|
||||
|
@ -59,6 +62,7 @@ set(sources
|
|||
Time/TimeManager.cxx
|
||||
Time/bodysolver.cxx
|
||||
Scripting/NasalSys.cxx
|
||||
Scripting/NasalAddons.cxx
|
||||
Scripting/NasalCondition.cxx
|
||||
Scripting/NasalAircraft.cxx
|
||||
Scripting/NasalString.cxx
|
||||
|
|
Loading…
Reference in a new issue