diff --git a/BUILD.bazel b/BUILD.bazel new file mode 100644 index 00000000..0bd4aa6a --- /dev/null +++ b/BUILD.bazel @@ -0,0 +1,110 @@ +load( + "@gz//bazel/skylark:build_defs.bzl", + "GZ_FEATURES", + "GZ_ROOT", + "GZ_VISIBILITY", + "gz_configure_header", + "gz_export_header", + "gz_include_header", +) +load( + "@gz//bazel/lint:lint.bzl", + "add_lint_tests", +) + +package( + default_visibility = GZ_VISIBILITY, + features = GZ_FEATURES, +) + +licenses(["notice"]) # Apache-2.0 + +exports_files(["LICENSE"]) + +gz_configure_header( + name = "fuel_tools_config_hh", + src = "include/gz/fuel_tools/config.hh.in", + cmakelists = ["CMakeLists.txt"], + defines = { + # These definitions are unused, + # this is merely to suppress generator warnings + "CMAKE_INSTALL_PREFIX": "unused", + }, + package = "fuel_tools", +) + +gz_export_header( + name = "include/gz/fuel_tools/Export.hh", + export_base = "GZ_FUEL_TOOLS", + lib_name = "gz-fuel_tools", + visibility = ["//visibility:private"], +) + +public_headers_no_gen = glob([ + "include/gz/fuel_tools/*.hh", +]) + +private_headers = glob(["src/*.hh"]) + +sources = glob( + ["src/*.cc"], + exclude = [ + "src/gz.cc", + "src/*_TEST.cc", + ], +) + +gz_include_header( + name = "fuel_tools_hh_genrule", + out = "include/gz/fuel_tools.hh", + hdrs = public_headers_no_gen + [ + "include/gz/fuel_tools/config.hh", + "include/gz/fuel_tools/Export.hh", + ], +) + +public_headers = public_headers_no_gen + [ + "include/gz/fuel_tools/config.hh", + "include/gz/fuel_tools/Export.hh", + "include/gz/fuel_tools.hh", +] + +cc_library( + name = "fuel_tools", + srcs = sources + private_headers, + hdrs = public_headers, + includes = ["include"], + deps = [ + GZ_ROOT + "common", + GZ_ROOT + "msgs", + "@curl", + "@jsoncpp", + "@yaml", + "@zip", + ], +) + +test_sources = glob( + include = ["src/*_TEST.cc"], + exclude = [ + "src/gz_TEST.cc", + "src/gz_src_TEST.cc", + ], +) + +[cc_test( + name = src.replace("/", "_").replace(".cc", "").replace("src_", ""), + srcs = [src], + env = { + "GZ_BAZEL": "1", + "GZ_BAZEL_PATH": "fuel_tools", + }, + deps = [ + ":fuel_tools", + GZ_ROOT + "common/testing", + "@gtest", + "@gtest//:gtest_main", + ], +) for src in test_sources] + +add_lint_tests() diff --git a/CMakeLists.txt b/CMakeLists.txt index 46882b23..bea828fa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,7 +54,7 @@ set(GZ_UTILS_VER ${gz-utils2_VERSION_MAJOR}) #-------------------------------------- # Find gz-common -gz_find_package(gz-common5 REQUIRED PRIVATE) +gz_find_package(gz-common5 REQUIRED PRIVATE COMPONENTS testing) set(GZ_COMMON_VER ${gz-common5_VERSION_MAJOR}) #-------------------------------------- @@ -70,6 +70,7 @@ set(GZ_MSGS_VER ${gz-msgs10_VERSION_MAJOR}) #-------------------------------------- # Find gz-tools find_program(HAVE_GZ_TOOLS gz) +set (GZ_TOOLS_VER 2) #============================================================================ # Configure the build diff --git a/Changelog.md b/Changelog.md index 31af227b..8cc03745 100644 --- a/Changelog.md +++ b/Changelog.md @@ -75,6 +75,20 @@ ## Gazebo Fuel Tools 7.x +### Gazebo Fuel Tools 7.3.0 (2023-06-13) + +1. Forward merges + * [Pull request #355](https://github.com/gazebosim/gz-fuel-tools/pull/355) + +1. Add bash completion + * [Pull request #329](https://github.com/gazebosim/gz-fuel-tools/pull/329) + +1. Reflect pagination of RESTful requests in iterator API + * [Pull request #350](https://github.com/gazebosim/gz-fuel-tools/pull/350) + +1. Support link referral download + * [Pull request #333](https://github.com/gazebosim/gz-fuel-tools/pull/333) + ### Gazebo Fuel Tools 7.2.2 (2023-03-29) 1. Support link referral download @@ -293,6 +307,19 @@ ### Gazebo Fuel Tools 4.8.3 (2023-03-29) +1. Support link referral download + * [Pull request #333](https://github.com/gazebosim/gz-fuel-tools/pull/333) + +### Gazebo Fuel Tools 4.9.0 (2023-05-03) + +1. Add bash completion + * [Pull request #329](https://github.com/gazebosim/gz-fuel-tools/pull/329) + +1. Reflect pagination of RESTful requests in iterator API + * [Pull request #350](https://github.com/gazebosim/gz-fuel-tools/pull/350) + +### Gazebo Fuel Tools 4.8.3 (2023-03-29) + 1. Support link referral download * [Pull request #333](https://github.com/gazebosim/gz-fuel-tools/pull/333) diff --git a/Migration.md b/Migration.md index 20d5ef6e..014416f4 100644 --- a/Migration.md +++ b/Migration.md @@ -1,3 +1,9 @@ +## Gazebo Fuel Tools 8.X to 9.X + +### Removals + +* `GZ_FUEL_INITIAL_CONFIG_PATH` macro removed from `gz/fuel_tools/config.hh` + ## Gazebo Fuel Tools 7.X to 8.X ### Deprecations diff --git a/include/gz/fuel_tools/FuelClient.hh b/include/gz/fuel_tools/FuelClient.hh index c8e5f6a8..d148de47 100644 --- a/include/gz/fuel_tools/FuelClient.hh +++ b/include/gz/fuel_tools/FuelClient.hh @@ -136,25 +136,46 @@ namespace gz /// \brief Returns models matching a given identifying criteria /// \param[in] _id a partially filled out identifier used to fetch models /// \remarks Fulfills Get-One requirement - /// \remarks It's not yet clear if model names are unique, so this API + /// \remarks Model names are unique to the owner, so this API /// allows the possibility of getting multiple models with the - /// same name. + /// same name if the owner is not specified. /// \return An iterator of models with names matching the criteria public: ModelIter Models(const ModelIdentifier &_id); /// \brief Returns models matching a given identifying criteria /// \param[in] _id a partially filled out identifier used to fetch models /// \remarks Fulfills Get-One requirement - /// \remarks It's not yet clear if model names are unique, so this API + /// \remarks Model names are unique to the owner, so this API /// allows the possibility of getting multiple models with the - /// same name. + /// same name if the owner is not specified. /// \return An iterator of models with names matching the criteria public: ModelIter Models(const ModelIdentifier &_id) const; - /// \brief Returns an iterator for the models found in a collection. - /// \param[in] _id a partially filled out identifier used to fetch a - /// collection. - /// \return An iterator of models in the collection. + /// \brief Returns models matching a given identifying criteria + /// \param[in] _id a partially filled out identifier used to fetch models + /// \param[in] _checkCache Whether to check the cache. + /// \remarks Fulfills Get-One requirement + /// \remarks Model names are unique to the owner, so this API + /// allows the possibility of getting multiple models with the + /// same name if the owner is not specified. + /// \return An iterator of models with names matching the criteria + public: ModelIter Models(const ModelIdentifier &_id, bool _checkCache); + + /// \brief Returns models matching a given identifying criteria + /// \param[in] _id a partially filled out identifier used to fetch models + /// \param[in] _checkCache Whether to check the cache. + /// \remarks Fulfills Get-One requirement + /// \remarks Model names are unique to the owner, so this API + /// allows the possibility of getting multiple models with the + /// same name if the owner is not specified. + /// \return An iterator of models with names matching the criteria + public: ModelIter Models(const ModelIdentifier &_id, + bool _checkCache) const; + + /// \brief Returns an iterator for the models found in a collection. + /// \param[in] _id a partially filled out identifier used to fetch a + /// collection. + /// \return An iterator of models in the collection. public: ModelIter Models(const CollectionIdentifier &_id) const; /// \brief Returns worlds matching a given identifying criteria diff --git a/include/gz/fuel_tools/config.hh.in b/include/gz/fuel_tools/config.hh.in index 74707ab8..28a909e0 100644 --- a/include/gz/fuel_tools/config.hh.in +++ b/include/gz/fuel_tools/config.hh.in @@ -30,6 +30,4 @@ #define GZ_FUEL_TOOLS_VERSION_HEADER "Gazebo Fuel Tools, version ${PROJECT_VERSION_FULL}\nCopyright (C) 2017 Open Source Robotics Foundation.\nReleased under the Apache 2.0 License.\n\n" -#define GZ_FUEL_INITIAL_CONFIG_PATH "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/ignition/${GZ_DESIGNATION}${PROJECT_VERSION_MAJOR}/" - #endif diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index aad437d8..c2ec97ac 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -70,6 +70,7 @@ gz_build_tests(TYPE UNIT TEST_LIST test_targets LIB_DEPS gz-common${GZ_COMMON_VER}::gz-common${GZ_COMMON_VER} + gz-common${GZ_COMMON_VER}::testing TINYXML2::TINYXML2 ) diff --git a/src/ClientConfig.cc b/src/ClientConfig.cc index f0d24ef2..1b11c76e 100644 --- a/src/ClientConfig.cc +++ b/src/ClientConfig.cc @@ -240,9 +240,9 @@ ClientConfig::ClientConfig(const ClientConfig &_copy) } ////////////////////////////////////////////////// -ClientConfig &ClientConfig::operator=(const ClientConfig &_rhs) +ClientConfig &ClientConfig::operator=(const ClientConfig &_copy) { - *(this->dataPtr) = *(_rhs.dataPtr); + *(this->dataPtr) = *(_copy.dataPtr); return *this; } @@ -526,7 +526,7 @@ std::string ClientConfig::AsString(const std::string &_prefix) const << _prefix << "Cache location: " << this->CacheLocation() << std::endl << _prefix << "Servers:" << std::endl; - for (auto s : this->Servers()) + for (const auto &s : this->Servers()) { out << _prefix << " ---" << std::endl; out << _prefix << s.AsString(" "); diff --git a/src/ClientConfig_TEST.cc b/src/ClientConfig_TEST.cc index ea672e27..efff93c0 100644 --- a/src/ClientConfig_TEST.cc +++ b/src/ClientConfig_TEST.cc @@ -20,28 +20,16 @@ #include #include #include +#include +#include #include + +#include "gz/fuel_tools/config.hh" #include "gz/fuel_tools/ClientConfig.hh" -#include "test_config.hh" using namespace gz; using namespace fuel_tools; -///////////////////////////////////////////////// -/// \brief Helper to remove file according to OS, while Windows -/// has this issue: -/// https://github.com/gazebosim/gz-common/issues/51 -/// \todo(anyone) Remove this once Windows issue is solved. -/// \param[in] _path Path to file to be removed. -void removeFileTemp(const std::string &_path) -{ -#ifndef _WIN32 - EXPECT_TRUE(gz::common::removeFile(_path)); -#else - gz::common::removeFile(_path); -#endif -} - ///////////////////////////////////////////////// /// \brief Get home directory. /// \return Home directory or empty string if home wasn't found. @@ -54,26 +42,34 @@ std::string homePath() #else gz::common::env("USERPROFILE", homePath); #endif - return homePath; } ///////////////////////////////////////////////// -/// \brief Get cache directory. -/// \return Cache directory -/// \ToDo: Move this function to gz::common::Filesystem -std::string cachePath() +class ClientConfigTest: public ::testing::Test { -#ifndef _WIN32 - return std::string("/tmp/gz/fuel"); -#else - return std::string("C:\\Windows\\Temp"); -#endif -} + public: void SetUp() override + { + gz::common::Console::SetVerbosity(4); + tempDir = gz::common::testing::MakeTestTempDirectory(); + ASSERT_TRUE(tempDir->Valid()) << tempDir->Path(); + + gz::common::chdir(tempDir->Path()); + } + + public: std::string cachePath() + { + return this->tempDir->Path(); + } + + public: std::shared_ptr tempDir; +}; + +class ServerConfigTest: public ClientConfigTest {}; ///////////////////////////////////////////////// /// \brief Initially only the default server in config -TEST(ClientConfig, InitiallyDefaultServers) +TEST_F(ClientConfigTest, InitiallyDefaultServers) { ClientConfig config; EXPECT_EQ(2u, config.Servers().size()); @@ -81,7 +77,7 @@ TEST(ClientConfig, InitiallyDefaultServers) ///////////////////////////////////////////////// /// \brief Servers can be added -TEST(ClientConfig, ServersCanBeAdded) +TEST_F(ClientConfigTest, ServersCanBeAdded) { ClientConfig config; ServerConfig srv; @@ -94,7 +90,7 @@ TEST(ClientConfig, ServersCanBeAdded) ///////////////////////////////////////////////// /// \brief We can load the default configuration file. -TEST(ClientConfig, CustomDefaultConfiguration) +TEST_F(ClientConfigTest, CustomDefaultConfiguration) { ClientConfig config; ASSERT_EQ(2u, config.Servers().size()); @@ -110,7 +106,7 @@ TEST(ClientConfig, CustomDefaultConfiguration) ///////////////////////////////////////////////// /// \brief We can load custom settings in a configuration file. -TEST(ClientConfig, CustomConfiguration) +TEST_F(ClientConfigTest, CustomConfiguration) { ClientConfig config; @@ -147,13 +143,11 @@ TEST(ClientConfig, CustomConfiguration) config.Servers().back().Url().Str()); EXPECT_EQ(cachePath(), config.CacheLocation()); - // Remove the configuration file. - removeFileTemp(testPath); } ///////////////////////////////////////////////// /// \brief A server contains an already used URL. -TEST(ClientConfig, RepeatedServerConfiguration) +TEST_F(ClientConfigTest, RepeatedServerConfiguration) { ClientConfig config; @@ -178,14 +172,11 @@ TEST(ClientConfig, RepeatedServerConfiguration) ofs.close(); EXPECT_TRUE(config.LoadConfig(testPath)); - - // Remove the configuration file. - removeFileTemp(testPath); } ///////////////////////////////////////////////// /// \brief A server without URL is not valid. -TEST(ClientConfig, NoServerUrlConfiguration) +TEST_F(ClientConfigTest, NoServerUrlConfiguration) { ClientConfig config; @@ -203,14 +194,11 @@ TEST(ClientConfig, NoServerUrlConfiguration) ofs.close(); EXPECT_FALSE(config.LoadConfig(testPath)); - - // Remove the configuration file. - removeFileTemp(testPath); } ///////////////////////////////////////////////// /// \brief A server with an empty URL is not valid. -TEST(ClientConfig, EmptyServerUrlConfiguration) +TEST_F(ClientConfigTest, EmptyServerUrlConfiguration) { ClientConfig config; @@ -228,14 +216,11 @@ TEST(ClientConfig, EmptyServerUrlConfiguration) ofs.close(); EXPECT_FALSE(config.LoadConfig(testPath)); - - // Remove the configuration file. - removeFileTemp(testPath); } ///////////////////////////////////////////////// /// \brief The "cache" option requires to set "path". -TEST(ClientConfig, NoCachePathConfiguration) +TEST_F(ClientConfigTest, NoCachePathConfiguration) { ClientConfig config; @@ -250,14 +235,11 @@ TEST(ClientConfig, NoCachePathConfiguration) ofs.close(); EXPECT_FALSE(config.LoadConfig(testPath)); - - // Remove the configuration file. - removeFileTemp(testPath); } ///////////////////////////////////////////////// /// \brief The path parameter cannot be empty. -TEST(ClientConfig, EmptyCachePathConfiguration) +TEST_F(ClientConfigTest, EmptyCachePathConfiguration) { ClientConfig config; @@ -273,13 +255,10 @@ TEST(ClientConfig, EmptyCachePathConfiguration) ofs.close(); EXPECT_FALSE(config.LoadConfig(testPath)); - - // Remove the configuration file. - removeFileTemp(testPath); } ///////////////////////////////////////////////// -TEST(ClientConfig, UserAgent) +TEST_F(ClientConfigTest, UserAgent) { ClientConfig config; EXPECT_EQ("IgnitionFuelTools-" GZ_FUEL_TOOLS_VERSION_FULL, @@ -290,7 +269,7 @@ TEST(ClientConfig, UserAgent) } ///////////////////////////////////////////////// -TEST(ServerConfig, ApiKey) +TEST_F(ServerConfigTest, ApiKey) { ServerConfig config; EXPECT_TRUE(config.ApiKey().empty()); @@ -303,7 +282,7 @@ TEST(ServerConfig, ApiKey) } ///////////////////////////////////////////////// -TEST(ClientConfig, AsString) +TEST_F(ClientConfigTest, AsString) { common::Console::SetVerbosity(4); { @@ -366,7 +345,7 @@ TEST(ClientConfig, AsString) } ///////////////////////////////////////////////// -TEST(ClientConfig, AsPrettyString) +TEST_F(ClientConfigTest, AsPrettyString) { common::Console::SetVerbosity(4); @@ -394,7 +373,7 @@ TEST(ClientConfig, AsPrettyString) } ///////////////////////////////////////////////// -TEST(ServerConfig, Url) +TEST_F(ServerConfigTest, Url) { // Invalid URL string { diff --git a/src/FuelClient.cc b/src/FuelClient.cc index d7acdea7..ea2b2fb3 100644 --- a/src/FuelClient.cc +++ b/src/FuelClient.cc @@ -399,16 +399,31 @@ WorldIter FuelClient::Worlds(const ServerConfig &_server) const ////////////////////////////////////////////////// ModelIter FuelClient::Models(const ModelIdentifier &_id) { - return const_cast(this)->Models(_id); + return this->Models(_id, true); } ////////////////////////////////////////////////// ModelIter FuelClient::Models(const ModelIdentifier &_id) const { - // Check local cache first - ModelIter localIter = this->dataPtr->cache->MatchingModels(_id); - if (localIter) - return localIter; + return this->Models(_id, true); +} + +////////////////////////////////////////////////// +ModelIter FuelClient::Models(const ModelIdentifier &_id, bool _checkCache) +{ + return const_cast(this)->Models(_id, _checkCache); +} + +////////////////////////////////////////////////// +ModelIter FuelClient::Models(const ModelIdentifier &_id, bool _checkCache) const +{ + if (_checkCache) + { + // Check local cache first + ModelIter localIter = this->dataPtr->cache->MatchingModels(_id); + if (localIter) + return localIter; + } // TODO(nkoenig) try to fetch model directly from a server // Note: gz-fuel-server doesn't like URLs ending in / @@ -419,8 +434,7 @@ ModelIter FuelClient::Models(const ModelIdentifier &_id) const path = path / _id.Owner() / "models"; if (path.Str().empty()) - // cppcheck-suppress identicalConditionAfterEarlyExit - return localIter; + return ModelIterFactory::Create(); gzmsg << _id.UniqueName() << " not found in cache, attempting download\n"; @@ -530,7 +544,7 @@ void FuelClient::AddServerConfigParametersToHeaders( std::vector &_headers) const { bool privateTokenDefined = false; - for (auto header : _headers) + for (const auto &header : _headers) { if (header.find("Private-token:") != std::string::npos) { @@ -627,7 +641,7 @@ Result FuelClient::DownloadModel(const ModelIdentifier &_id, if(!res) return res; - for (ModelIdentifier dep : dependencies) + for (const ModelIdentifier &dep : dependencies) { // Download dependency if not in the local cache if (!this->dataPtr->cache->MatchingModel(dep)) @@ -787,7 +801,7 @@ Result FuelClient::ModelDependencies( std::vector &_dependencies) { std::vector newDeps; - for (auto modelId : _ids) + for (const auto &modelId : _ids) { std::vector modelDeps; auto result = this->ModelDependencies(modelId, modelDeps); @@ -946,7 +960,7 @@ std::vector FuelClient::DownloadModels( std::lock_guard lock(idsMutex); gzdbg << "Adding " << dependencies.size() << " model dependencies to queue from " << id.Name() << "\n"; - for (auto dep : dependencies) + for (const auto &dep : dependencies) { if (uniqueIds.count(dep) == 0) { @@ -1191,15 +1205,15 @@ bool FuelClient::ParseWorldUrl(const common::URI &_worldUrl, } ////////////////////////////////////////////////// -bool FuelClient::ParseModelFileUrl(const common::URI &_fileUrl, +bool FuelClient::ParseModelFileUrl(const common::URI &_modelFileUrl, ModelIdentifier &_id, std::string &_filePath) { - if (!_fileUrl.Valid()) + if (!_modelFileUrl.Valid()) return false; - this->dataPtr->CheckForDeprecatedUri(_fileUrl); + this->dataPtr->CheckForDeprecatedUri(_modelFileUrl); - auto urlStr = _fileUrl.Str(); + auto urlStr = _modelFileUrl.Str(); std::smatch match; std::string scheme; @@ -1262,13 +1276,13 @@ bool FuelClient::ParseModelFileUrl(const common::URI &_fileUrl, } ////////////////////////////////////////////////// -bool FuelClient::ParseWorldFileUrl(const common::URI &_fileUrl, +bool FuelClient::ParseWorldFileUrl(const common::URI &_worldFileUrl, WorldIdentifier &_id, std::string &_filePath) { - if (!_fileUrl.Valid()) + if (!_worldFileUrl.Valid()) return false; - auto urlStr = _fileUrl.Str(); + auto urlStr = _worldFileUrl.Str(); std::smatch match; std::string scheme; @@ -1935,7 +1949,7 @@ void FuelClientPrivate::CheckForDeprecatedUri(const common::URI &_uri) { std::string newUrl = _uri.Str(); newUrl.replace(ignFuelPos, oldServer.size(), "fuel.gazebosim.org"); - ignwarn << "The " << oldServer << " URL is deprecrated. Pleasse change " + gzwarn << "The " << oldServer << " URL is deprecrated. Pleasse change " << _uri.Str() << " to " << newUrl << std::endl; } } @@ -1959,7 +1973,7 @@ void FuelClientPrivate::ZipFromResponse(const RestResponse &_resp, // Check for valid URI if (common::URI::Valid(linkUri)) { - igndbg << "Downloading from a referral link [" << linkUri << "]\n"; + gzdbg << "Downloading from a referral link [" << linkUri << "]\n"; // Get the zip data. RestResponse linkResp = rest.Request(HttpMethod::GET, // URL @@ -1979,7 +1993,7 @@ void FuelClientPrivate::ZipFromResponse(const RestResponse &_resp, } else { - ignerr << "Invalid referral link URI [" << linkUri << "]. " + gzerr << "Invalid referral link URI [" << linkUri << "]. " << "Unable to download.\n"; } } @@ -1992,7 +2006,7 @@ void FuelClientPrivate::ZipFromResponse(const RestResponse &_resp, } else { - ignerr << "Invalid content-type of [" << contentTypeIter->second << "]. " + gzerr << "Invalid content-type of [" << contentTypeIter->second << "]. " << "Unable to download.\n"; } } diff --git a/src/FuelClient_TEST.cc b/src/FuelClient_TEST.cc index 38385bcd..ce4c462e 100644 --- a/src/FuelClient_TEST.cc +++ b/src/FuelClient_TEST.cc @@ -26,15 +26,7 @@ #include "gz/fuel_tools/Result.hh" #include "gz/fuel_tools/WorldIdentifier.hh" -#include "test_config.hh" - -#ifdef _WIN32 -#include -#define ChangeDirectory _chdir -#else -#include -#define ChangeDirectory chdir -#endif +#include using namespace gz; using namespace gz::fuel_tools; @@ -126,12 +118,21 @@ void createLocalWorld(ClientConfig &_conf) } ///////////////////////////////////////////////// -class FuelClientTest : public ::testing::Test +class FuelClientTest: public ::testing::Test { public: void SetUp() override { gz::common::Console::SetVerbosity(4); + tempDir = gz::common::testing::MakeTestTempDirectory(); + ASSERT_TRUE(tempDir->Valid()) << tempDir->Path(); + + gz::common::chdir(tempDir->Path()); + ASSERT_FALSE(common::exists("test_cache")); + ASSERT_TRUE(common::createDirectories("test_cache")); + ASSERT_TRUE(common::isDirectory("test_cache")); } + + public: std::shared_ptr tempDir; }; ///////////////////////////////////////////////// @@ -414,10 +415,6 @@ INSTANTIATE_TEST_SUITE_P( // https://github.com/gazebosim/gz-fuel-tools/issues/105 TEST_P(FuelClientDownloadTest, DownloadModel) { - // Configure to use binary path as cache - ASSERT_EQ(0, ChangeDirectory(PROJECT_BINARY_PATH)); - common::removeAll("test_cache"); - ASSERT_TRUE(common::createDirectories("test_cache")); ClientConfig config; config.SetCacheLocation(common::joinPaths(common::cwd(), "test_cache")); @@ -621,10 +618,6 @@ TEST_P(FuelClientDownloadTest, DownloadModel) // https://github.com/gazebosim/gz-fuel-tools/issues/106 TEST_F(FuelClientTest, GZ_UTILS_TEST_DISABLED_ON_WIN32(ModelDependencies)) { - // Configure to use binary path as cache - ASSERT_EQ(0, ChangeDirectory(PROJECT_BINARY_PATH)); - common::removeAll("test_cache"); - ASSERT_TRUE(common::createDirectories("test_cache")); ClientConfig config; config.SetCacheLocation(common::joinPaths(common::cwd(), "test_cache")); @@ -697,10 +690,6 @@ TEST_F(FuelClientTest, GZ_UTILS_TEST_DISABLED_ON_WIN32(ModelDependencies)) // See https://github.com/gazebosim/gz-fuel-tools/issues/231 TEST_F(FuelClientTest, GZ_UTILS_TEST_DISABLED_ON_WIN32(CachedModel)) { - // Configure to use binary path as cache and populate it - ASSERT_EQ(0, ChangeDirectory(PROJECT_BINARY_PATH)); - common::removeAll("test_cache"); - ASSERT_TRUE(common::createDirectories("test_cache")); ClientConfig config; config.SetCacheLocation(common::joinPaths(common::cwd(), "test_cache")); createLocalModel(config); @@ -1089,11 +1078,6 @@ TEST_F(FuelClientTest, ParseWorldFileUrl) // https://github.com/gazebosim/gz-fuel-tools/issues/105 TEST_F(FuelClientTest, DownloadWorld) { - // Configure to use binary path as cache - ASSERT_EQ(0, ChangeDirectory(PROJECT_BINARY_PATH)); - common::removeAll("test_cache"); - ASSERT_TRUE(common::createDirectories("test_cache")); - ServerConfig server; server.SetUrl(common::URI( "https://fuel.gazebosim.org")); @@ -1167,10 +1151,6 @@ TEST_F(FuelClientTest, DownloadWorld) // https://github.com/gazebosim/gz-fuel-tools/issues/106 TEST_F(FuelClientTest, CachedWorld) { - // Configure to use binary path as cache and populate it - ASSERT_EQ(0, ChangeDirectory(PROJECT_BINARY_PATH)); - common::removeAll("test_cache"); - ASSERT_TRUE(common::createDirectories("test_cache")); ClientConfig config; config.SetCacheLocation(common::joinPaths(common::cwd(), "test_cache")); createLocalWorld(config); @@ -1386,6 +1366,52 @@ TEST_F(FuelClientTest, Models) } } +////////////////////////////////////////////////// +TEST_F(FuelClientTest, ModelsCheckCached) +{ + ClientConfig config; + std::string cacheDir = common::joinPaths(common::cwd(), "test_cache"); + common::removeAll(cacheDir ); + ASSERT_TRUE(common::createDirectories(cacheDir)); + config.SetCacheLocation(cacheDir); + FuelClient client(config); + ServerConfig serverConfig; + ModelIdentifier modelId; + modelId.SetOwner("openroboticstest"); + std::vector modelInfos; + { + for (ModelIter iter = client.Models(modelId, true); iter; ++iter) + { + modelInfos.push_back(*iter); + } + } + ASSERT_FALSE(modelInfos.empty()); + // Download one of the models to test the behavior of Models() with + // different values for _checkCache + EXPECT_TRUE(client.DownloadModel(modelInfos.front().Identification())); + { + std::size_t counter = 0; + for (ModelIter iter = client.Models(modelId, true); iter; ++iter, ++counter) + { + } + std::cout << "counter: " << counter << std::endl; + // Expect only one result with checkCache=true because we've downloaded only + // one model + EXPECT_EQ(counter, 1u); + EXPECT_GT(modelInfos.size(), counter); + } + + { + std::size_t counter = 0; + for (ModelIter iter = client.Models(modelId, false); iter; + ++iter, ++counter) + { + } + std::cout << "counter: " << counter << std::endl; + EXPECT_EQ(counter, modelInfos.size()); + } +} + ///////////////////////////////////////////////// // Protocol "https" not supported or disabled in libcurl for Windows // https://github.com/gazebosim/gz-fuel-tools/issues/105 diff --git a/src/Interface.cc b/src/Interface.cc index d2c5e82e..c1c9412a 100644 --- a/src/Interface.cc +++ b/src/Interface.cc @@ -140,7 +140,7 @@ namespace gz if (!basename.empty() && basename.find(".sdf") != std::string::npos) { std::string extension = basename.substr( - basename.find_last_of(".") + 1); + basename.find_last_of('.') + 1); if (extension == "sdf") return *dirIter; } diff --git a/src/Interface_TEST.cc b/src/Interface_TEST.cc index e389b4ee..3d5435c4 100644 --- a/src/Interface_TEST.cc +++ b/src/Interface_TEST.cc @@ -23,30 +23,34 @@ #include "gz/fuel_tools/FuelClient.hh" #include "gz/fuel_tools/Interface.hh" -#include "test_config.hh" - -#ifdef _WIN32 -#include -#define ChangeDirectory _chdir -#else -#include -#define ChangeDirectory chdir -#endif +#include using namespace gz; using namespace gz::fuel_tools; +///////////////////////////////////////////////// +class InterfaceTest: public ::testing::Test +{ + public: void SetUp() override + { + gz::common::Console::SetVerbosity(4); + tempDir = gz::common::testing::MakeTestTempDirectory(); + ASSERT_TRUE(tempDir->Valid()) << tempDir->Path(); + + gz::common::chdir(tempDir->Path()); + ASSERT_FALSE(common::exists("test_cache")); + ASSERT_TRUE(common::createDirectories("test_cache")); + ASSERT_TRUE(common::isDirectory("test_cache")); + } + + public: std::shared_ptr tempDir; +}; + ///////////////////////////////////////////////// // Protocol "https" not supported or disabled in libcurl for Windows // https://github.com/gazebosim/gz-fuel-tools/issues/105 -TEST(Interface, FetchResources) +TEST_F(InterfaceTest, FetchResources) { - common::Console::SetVerbosity(4); - - // Configure to use binary path as cache - ASSERT_EQ(0, ChangeDirectory(PROJECT_BINARY_PATH)); - common::removeAll("test_cache"); - ASSERT_TRUE(common::createDirectories("test_cache")); ClientConfig config; config.SetCacheLocation(common::joinPaths(common::cwd(), "test_cache")); diff --git a/src/LocalCache_TEST.cc b/src/LocalCache_TEST.cc index 15b93384..4e9844b4 100644 --- a/src/LocalCache_TEST.cc +++ b/src/LocalCache_TEST.cc @@ -22,21 +22,13 @@ #include #include #include +#include #include #include "gz/fuel_tools/ClientConfig.hh" #include "gz/fuel_tools/WorldIdentifier.hh" #include "LocalCache.hh" -#include "test_config.hh" - -#ifdef _WIN32 -#include -#define ChangeDirectory _chdir -#else -#include -#define ChangeDirectory chdir -#endif using namespace gz; using namespace fuel_tools; @@ -219,7 +211,16 @@ class LocalCacheTest : public ::testing::Test public: void SetUp() override { gz::common::Console::SetVerbosity(4); + tempDir = gz::common::testing::MakeTestTempDirectory(); + ASSERT_TRUE(tempDir->Valid()) << tempDir->Path(); + + gz::common::chdir(tempDir->Path()); + ASSERT_FALSE(common::exists("test_cache")); + ASSERT_TRUE(common::createDirectories("test_cache")); + ASSERT_TRUE(common::isDirectory("test_cache")); } + + public: std::shared_ptr tempDir; }; ///////////////////////////////////////////////// @@ -227,9 +228,6 @@ class LocalCacheTest : public ::testing::Test // See https://github.com/gazebosim/gz-fuel-tools/issues/231 TEST_F(LocalCacheTest, GZ_UTILS_TEST_DISABLED_ON_WIN32(AllModels)) { - ASSERT_EQ(0, ChangeDirectory(PROJECT_BINARY_PATH)); - EXPECT_TRUE(common::removeAll("test_cache")); - ASSERT_TRUE(common::createDirectories("test_cache")); ClientConfig conf; conf.SetCacheLocation(common::joinPaths(common::cwd(), "test_cache")); createLocal6Models(conf); @@ -256,9 +254,6 @@ TEST_F(LocalCacheTest, GZ_UTILS_TEST_DISABLED_ON_WIN32(AllModels)) // See https://github.com/gazebosim/gz-fuel-tools/issues/307 TEST_F(LocalCacheTest, GZ_UTILS_TEST_DISABLED_ON_WIN32(MatchingModels)) { - ASSERT_EQ(0, ChangeDirectory(PROJECT_BINARY_PATH)); - EXPECT_TRUE(common::removeAll("test_cache")); - ASSERT_TRUE(common::createDirectories("test_cache")); ClientConfig conf; conf.Clear(); conf.SetCacheLocation(common::joinPaths(common::cwd(), "test_cache")); @@ -303,9 +298,6 @@ TEST_F(LocalCacheTest, GZ_UTILS_TEST_DISABLED_ON_WIN32(MatchingModels)) // See https://github.com/gazebosim/gz-fuel-tools/issues/307 TEST_F(LocalCacheTest, GZ_UTILS_TEST_DISABLED_ON_WIN32(MatchingModel)) { - ASSERT_EQ(0, ChangeDirectory(PROJECT_BINARY_PATH)); - EXPECT_TRUE(common::removeAll("test_cache")); - ASSERT_TRUE(common::createDirectories("test_cache")); ClientConfig conf; conf.SetCacheLocation(common::joinPaths(common::cwd(), "test_cache")); createLocal6Models(conf); @@ -360,9 +352,6 @@ TEST_F(LocalCacheTest, GZ_UTILS_TEST_DISABLED_ON_WIN32(MatchingModel)) // See https://github.com/gazebosim/gz-fuel-tools/issues/307 TEST_F(LocalCacheTest, GZ_UTILS_TEST_DISABLED_ON_WIN32(AllWorlds)) { - ASSERT_EQ(0, ChangeDirectory(PROJECT_BINARY_PATH)); - EXPECT_TRUE(common::removeAll("test_cache")); - ASSERT_TRUE(common::createDirectories("test_cache")); ClientConfig conf; conf.SetCacheLocation(common::joinPaths(common::cwd(), "test_cache")); createLocal6Worlds(conf); @@ -393,9 +382,6 @@ TEST_F(LocalCacheTest, GZ_UTILS_TEST_DISABLED_ON_WIN32(AllWorlds)) // See https://github.com/gazebosim/gz-fuel-tools/issues/307 TEST_F(LocalCacheTest, GZ_UTILS_TEST_DISABLED_ON_WIN32(MatchingWorlds)) { - ASSERT_EQ(0, ChangeDirectory(PROJECT_BINARY_PATH)); - EXPECT_TRUE(common::removeAll("test_cache")); - ASSERT_TRUE(common::createDirectories("test_cache")); ClientConfig conf; conf.Clear(); conf.SetCacheLocation(common::joinPaths(common::cwd(), "test_cache")); @@ -428,9 +414,6 @@ TEST_F(LocalCacheTest, GZ_UTILS_TEST_DISABLED_ON_WIN32(MatchingWorlds)) // See https://github.com/gazebosim/gz-fuel-tools/issues/307 TEST_F(LocalCacheTest, GZ_UTILS_TEST_DISABLED_ON_WIN32(MatchingWorld)) { - ASSERT_EQ(0, ChangeDirectory(PROJECT_BINARY_PATH)); - EXPECT_TRUE(common::removeAll("test_cache")); - ASSERT_TRUE(common::createDirectories("test_cache")); ClientConfig conf; conf.SetCacheLocation(common::joinPaths(common::cwd(), "test_cache")); createLocal6Worlds(conf); diff --git a/src/Model.cc b/src/Model.cc index 5d760fdb..90f85560 100644 --- a/src/Model.cc +++ b/src/Model.cc @@ -24,9 +24,8 @@ #include "ModelPrivate.hh" -namespace ignft = gz::fuel_tools; using namespace gz; -using namespace ignft; +using namespace fuel_tools; ////////////////////////////////////////////////// Model::Model() : dataPtr(nullptr) diff --git a/src/ModelIdentifier.cc b/src/ModelIdentifier.cc index 2e96c43f..45df4b4d 100644 --- a/src/ModelIdentifier.cc +++ b/src/ModelIdentifier.cc @@ -174,10 +174,10 @@ bool ModelIdentifier::SetName(const std::string &_name) } ////////////////////////////////////////////////// -bool ModelIdentifier::SetOwner(const std::string &_owner) +bool ModelIdentifier::SetOwner(const std::string &_name) { bool success = false; - std::string owner = common::lowercase(_owner); + std::string owner = common::lowercase(_name); if (this->dataPtr->ValidName(owner)) { success = true; diff --git a/src/ModelIter.cc b/src/ModelIter.cc index 8829899b..c9db28b3 100644 --- a/src/ModelIter.cc +++ b/src/ModelIter.cc @@ -148,65 +148,55 @@ IterRestIds::~IterRestIds() { } +std::vector IterRestIds::ParseIdsFromResponse( + const RestResponse &resp) +{ + if (resp.data == "null\n" || resp.statusCode != 200) + return {}; + + // Parse the response. + return JSONParser::ParseModels(resp.data, this->config); +} + ////////////////////////////////////////////////// IterRestIds::IterRestIds(const Rest &_rest, const ServerConfig &_config, const std::string &_api) - : config(_config), rest(_rest) + : config(_config), rest(_rest), api(_api) +{ + this->idIter = this->ids.begin(); + this->Next(); +} + +////////////////////////////////////////////////// +RestResponse IterRestIds::MakeRestRequest(std::size_t _page) { HttpMethod method = HttpMethod::GET; - this->config = _config; - int page = 1; std::vector headers = {"Accept: application/json"}; - RestResponse resp; std::vector modelIds; - this->ids.clear(); - - do - { - // Prepare the request with the next page. - std::string queryStrPage = "page=" + std::to_string(page); - std::string path = _api; - ++page; - - // Fire the request. - resp = this->rest.Request(method, this->config.Url().Str(), + // Prepare the request with the requested page. + std::string queryStrPage = "page=" + std::to_string(_page); + std::string path = this->api; + // Fire the request. + return this->rest.Request(method, this->config.Url().Str(), this->config.Version(), std::regex_replace(path, std::regex(R"(\\)"), "/"), {queryStrPage}, headers, ""); - - // TODO(nkoenig): resp.statusCode should return != 200 when the page - // requested does - // not exist. When this happens we should stop without calling ParseModels() - if (resp.data == "null\n" || resp.statusCode != 200) - break; - - // Parse the response. - modelIds = JSONParser::ParseModels(resp.data, this->config); - - // Add the vector of models to the list. - this->ids.insert(std::end(this->ids), std::begin(modelIds), - std::end(modelIds)); - } while (!modelIds.empty()); - - if (this->ids.empty()) - return; - - this->idIter = this->ids.begin(); - - // make first model - std::shared_ptr ptr(new ModelPrivate); - ptr->id = *(this->idIter); - ptr->id.SetServer(this->config); - this->model = Model(ptr); - - gzdbg << "Got response [" << resp.data << "]\n"; } ////////////////////////////////////////////////// void IterRestIds::Next() { // advance pointer - ++(this->idIter); + if (this->idIter != this->ids.end()) + ++(this->idIter); + + if (this->idIter == this->ids.end()) + { + ++this->currentPage; + RestResponse resp = this->MakeRestRequest(this->currentPage); + this->ids = this->ParseIdsFromResponse(resp); + this->idIter = this->ids.begin(); + } // Update personal model class if (this->idIter != this->ids.end()) @@ -216,7 +206,6 @@ void IterRestIds::Next() ptr->id.SetServer(this->config); this->model = Model(ptr); } - // TODO(nkoenig) request next page if api is paginated } ////////////////////////////////////////////////// @@ -257,7 +246,6 @@ ModelIter::operator bool() const ////////////////////////////////////////////////// ModelIter &ModelIter::operator++() { - // TODO(nkoenig) Request more data if there are more pages if (!this->dataPtr->HasReachedEnd()) { this->dataPtr->Next(); diff --git a/src/ModelIterPrivate.hh b/src/ModelIterPrivate.hh index 0b58d933..0f6787f0 100644 --- a/src/ModelIterPrivate.hh +++ b/src/ModelIterPrivate.hh @@ -130,7 +130,7 @@ namespace gz }; /// \brief class for iterating through model ids from a rest API - class GZ_FUEL_TOOLS_VISIBLE IterRestIds: public ModelIterPrivate + class GZ_FUEL_TOOLS_HIDDEN IterRestIds: public ModelIterPrivate { /// \brief constructor public: IterRestIds(const Rest &_rest, @@ -153,11 +153,29 @@ namespace gz /// \brief RESTful client public: Rest rest; + /// \brief The API (path) of the RESTful requests + public: const std::string api; + + /// \brief Make a RESTful request for the given page + /// \param[in] _page Page number to request + /// \return Response from the request + protected: RestResponse MakeRestRequest(std::size_t _page); + + /// \brief Parse model identifiers from a RESTful response + /// \param[in] _resp RESTful response + /// \return A vector of identifiers extracted from the response. + protected: std::vector ParseIdsFromResponse( + const RestResponse &_resp); + /// \brief Model identifiers in the current page protected: std::vector ids; /// \brief Where the current iterator is in the list of ids protected: std::vector::iterator idIter; + + /// \brief Keep track of page number for pagination of response data from + /// server. + protected: std::size_t currentPage{0}; }; } } diff --git a/src/Model_TEST.cc b/src/Model_TEST.cc index 620341d3..a30502da 100644 --- a/src/Model_TEST.cc +++ b/src/Model_TEST.cc @@ -18,9 +18,8 @@ #include #include "gz/fuel_tools/Model.hh" -namespace ignft = gz::fuel_tools; using namespace gz; -using namespace ignft; +using namespace fuel_tools; ///////////////////////////////////////////////// /// \brief Nothing crashes diff --git a/src/RestClient_TEST.cc b/src/RestClient_TEST.cc index b88c93b9..748794b1 100644 --- a/src/RestClient_TEST.cc +++ b/src/RestClient_TEST.cc @@ -18,7 +18,6 @@ #include #include #include "gz/fuel_tools/RestClient.hh" -#include "test_config.hh" ///////////////////////////////////////////////// TEST(RestClient, UserAgent) diff --git a/src/WorldIdentifier.cc b/src/WorldIdentifier.cc index 38697476..9c9296a0 100644 --- a/src/WorldIdentifier.cc +++ b/src/WorldIdentifier.cc @@ -106,9 +106,9 @@ std::string WorldIdentifier::Owner() const } ////////////////////////////////////////////////// -bool WorldIdentifier::SetOwner(const std::string &_owner) +bool WorldIdentifier::SetOwner(const std::string &_name) { - this->dataPtr->owner = common::lowercase(_owner); + this->dataPtr->owner = common::lowercase(_name); return true; } @@ -177,9 +177,9 @@ std::string WorldIdentifier::LocalPath() const } ////////////////////////////////////////////////// -bool WorldIdentifier::SetLocalPath(const std::string &_localPath) +bool WorldIdentifier::SetLocalPath(const std::string &_path) { - this->dataPtr->localPath = _localPath; + this->dataPtr->localPath = _path; return true; } diff --git a/src/Zip.cc b/src/Zip.cc index c4d88da6..553c626f 100644 --- a/src/Zip.cc +++ b/src/Zip.cc @@ -37,7 +37,7 @@ bool CompressFile(zip *_archive, const std::string &_file, { if (gz::common::isDirectory(_file)) { - if (zip_add_dir(_archive, _entry.c_str()) < 0) + if (zip_dir_add(_archive, _entry.c_str(), 0) < 0) { gzerr << "Error adding directory to zip: " << _file << std::endl; return false; @@ -69,7 +69,7 @@ bool CompressFile(zip *_archive, const std::string &_file, return false; } - if (zip_add(_archive, _entry.c_str(), source) + if (zip_file_add(_archive, _entry.c_str(), source, 0) < 0) { gzerr << "Error adding file to zip: " << _file << std::endl; diff --git a/src/cmd/CMakeLists.txt b/src/cmd/CMakeLists.txt index c546769a..6c027466 100644 --- a/src/cmd/CMakeLists.txt +++ b/src/cmd/CMakeLists.txt @@ -1,17 +1,60 @@ -# Generate a the ruby script. +#=============================================================================== +# Generate the ruby script for internal testing. # Note that the major version of the library is included in the name. # Ex: cmdfuel0.rb -if (APPLE) - set(GZ_LIBRARY_NAME lib${PROJECT_NAME_LOWER}.dylib) -elseif(MSVC) - set(GZ_LIBRARY_NAME ${PROJECT_NAME_LOWER}.dll) -else() - set(GZ_LIBRARY_NAME lib${PROJECT_NAME_LOWER}.so) -endif() +# Unlike other gz libraries, the ruby script for the fuel_tools library is called cmdfuel.rb instead of cmdfuel_tools.rb +set(CMD_NAME cmdfuel) +set(cmd_script_generated_test "${CMAKE_BINARY_DIR}/test/lib/$/ruby/gz/${CMD_NAME}${PROJECT_VERSION_MAJOR}.rb") +set(cmd_script_configured_test "${CMAKE_CURRENT_BINARY_DIR}/test_${CMD_NAME}${PROJECT_VERSION_MAJOR}.rb.configured") + +# Set the library_location variable to the full path of the library file within +# the build directory. +set(library_location "$") + +configure_file( + "${CMD_NAME}.rb.in" + "${cmd_script_configured_test}" + @ONLY) + +file(GENERATE + OUTPUT "${cmd_script_generated_test}" + INPUT "${cmd_script_configured_test}") + + +#=============================================================================== +# Used for the installed version. +# Generate the ruby script that gets installed. +# Note that the major version of the library is included in the name. +# Ex: cmdfuel0.rb +set(cmd_script_generated "${CMAKE_CURRENT_BINARY_DIR}/$/${CMD_NAME}${PROJECT_VERSION_MAJOR}.rb") +set(cmd_script_configured "${CMAKE_CURRENT_BINARY_DIR}/${CMD_NAME}${PROJECT_VERSION_MAJOR}.rb.configured") + +# Set the library_location variable to the relative path to the library file +# within the install directory structure. +set(library_location "../../../${CMAKE_INSTALL_LIBDIR}/$") configure_file( - "cmdfuel.rb.in" - "${CMAKE_CURRENT_BINARY_DIR}/cmdfuel${PROJECT_VERSION_MAJOR}.rb" @ONLY) + "${CMD_NAME}.rb.in" + "${cmd_script_configured}" + @ONLY) + +file(GENERATE + OUTPUT "${cmd_script_generated}" + INPUT "${cmd_script_configured}") # Install the ruby command line library in an unversioned location. -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/cmdfuel${PROJECT_VERSION_MAJOR}.rb DESTINATION lib/ruby/gz) +install(FILES ${cmd_script_generated} DESTINATION lib/ruby/gz) + + +#=============================================================================== +# Bash completion + +# Tack version onto and install the bash completion script +configure_file( + "fuel.bash_completion.sh" + "${CMAKE_CURRENT_BINARY_DIR}/fuel${PROJECT_VERSION_MAJOR}.bash_completion.sh" @ONLY) +install( + FILES + ${CMAKE_CURRENT_BINARY_DIR}/fuel${PROJECT_VERSION_MAJOR}.bash_completion.sh + DESTINATION + ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/gz/gz${GZ_TOOLS_VER}.completion.d) diff --git a/src/cmd/cmdfuel.rb.in b/src/cmd/cmdfuel.rb.in index 283c7245..632d9443 100755 --- a/src/cmd/cmdfuel.rb.in +++ b/src/cmd/cmdfuel.rb.in @@ -28,7 +28,7 @@ end require 'optparse' # Constants. -LIBRARY_NAME = '@GZ_LIBRARY_NAME@' +LIBRARY_NAME = '@library_location@' LIBRARY_VERSION = '@PROJECT_VERSION_FULL@' MAX_PARALLEL_JOBS = 16 @@ -45,10 +45,10 @@ COMMON_OPTIONS = " The following information is in regards to user authentication via \n"\ " the --header command line option. \n"\ " \n"\ - " Two types of credentials are supported on Gazebo Fuel, Private \n"\ + " Two types of credentials are supported on Gazebo Fuel, Private \n"\ " Token and JSON Web Token(JWT). The Private Token method is prefered. \n"\ " Private tokens can be created through your user settings on \n"\ - " https://app.gazebosim.org. Example usage: \n"\ + " https://app.gazebosim.org. Example usage: \n"\ " 1. Private token method: \n"\ " --header 'Private-Token: ' \n"\ " 2. JWT method: \n"\ @@ -58,7 +58,7 @@ COMMON_OPTIONS = COMMANDS = { 'fuel' => "Manage simulation resources. \n"\ " \n"\ - " gz fuel [action] [options] \n"\ + " gz fuel [action] [options] \n"\ " \n"\ "Available Actions: \n"\ " delete Delete resources \n"\ @@ -75,15 +75,15 @@ COMMANDS = { 'fuel' => " arguments for level 3. \n" + COMMON_OPTIONS + "\n\n" + "Environment variables: \n"\ - " GZ_FUEL_CACHE_PATH Path to the cache where resources are \n"\ - " downloaded to. Defaults to $HOME/.gz/fuel \n" + " GZ_FUEL_CACHE_PATH Path to the cache where resources are \n"\ + " downloaded to. Defaults to $HOME/.gz/fuel \n" } SUBCOMMANDS = { 'delete' => "Delete simulation resources \n"\ " \n"\ - " gz fuel delete [options] \n"\ + " gz fuel delete [options] \n"\ " \n"\ "Available Options: \n"\ " -u [--url] arg URL of the server that should receive \n"\ @@ -130,7 +130,7 @@ SUBCOMMANDS = { 'list' => "List simulation resources \n"\ " \n"\ - " gz fuel list [options] \n"\ + " gz fuel list [options] \n"\ " \n"\ "Available Options: \n"\ " -t [--type] arg Resource type (i.e. model, world). Required. \n"\ @@ -144,7 +144,7 @@ SUBCOMMANDS = { 'meta' => "Read and write resource metadata \n"\ " \n"\ - " gz fuel meta [options] \n"\ + " gz fuel meta [options] \n"\ " \n"\ "Available Options: \n"\ " --config2pbtxt arg Convert a model.config file to a \n"\ @@ -350,7 +350,15 @@ class Cmd options = parse(args) # Read the plugin that handles the command. - plugin = LIBRARY_NAME + if LIBRARY_NAME[0] == '/' + # If the first character is a slash, we'll assume that we've been given an + # absolute path to the library. This is only used during test mode. + plugin = LIBRARY_NAME + else + # We're assuming that the library path is relative to the current + # location of this script. + plugin = File.expand_path(File.join(File.dirname(__FILE__), LIBRARY_NAME)) + end conf_version = LIBRARY_VERSION begin @@ -452,7 +460,7 @@ class Cmd end rescue puts "Library error: Problem running [#{options['subcommand']}]() "\ - "from @GZ_LIBRARY_NAME@." + "from #{plugin}." end # begin end # execute end # class diff --git a/src/cmd/fuel.bash_completion.sh b/src/cmd/fuel.bash_completion.sh new file mode 100755 index 00000000..d29f4a21 --- /dev/null +++ b/src/cmd/fuel.bash_completion.sh @@ -0,0 +1,191 @@ +#!/usr/bin/env bash +# +# Copyright (C) 2023 Open Source Robotics Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# bash tab-completion + +# This is a per-library function definition, used in conjunction with the +# top-level entry point in ign-tools. + +GZ_FUEL_SUBCOMMANDS=" +delete +download +edit +list +meta +upload +" + +# TODO: In Fortress+, for each of the completion lists, remove --force-version +# and --versions. Add --help-all. Update ../gz_TEST.cc accordingly. +GZ_FUEL_COMPLETION_LIST=" + -c --config + -h --help + -v --verbose + --force-version + --versions +" + +GZ_DELETE_COMPLETION_LIST=" + --header + -c --config + -h --help + -u --url + --force-version + --versions +" + +GZ_DOWNLOAD_COMPLETION_LIST=" + --header + -c --config + -h --help + -j --jobs + -t --type + -u --url + --force-version + --versions +" + +GZ_EDIT_COMPLETION_LIST=" + --header + -b --public + -c --config + -h -help + -m --model + -p --private + -u --url + --force-version + --versions +" + +GZ_LIST_COMPLETION_LIST=" + -c --config + -h --help + -o --owner + -r --raw + -t --type + -u --url + --force-version + --versions +" + +GZ_META_COMPLETION_LIST=" + --config2pbtxt + --pbtxt2config + -c --config + -h --help + --force-version + --versions +" + +GZ_UPLOAD_COMPLETION_LIST=" + --header + -c --config + -h --help + -m --model + -p --private + -u --url + --force-version + --versions +" + +function __get_comp_from_list { + if [[ ${COMP_WORDS[COMP_CWORD]} == -* ]]; then + # Specify options (-*) word list for this subcommand + COMPREPLY=($(compgen -W "$@" \ + -- "${COMP_WORDS[COMP_CWORD]}" )) + return + else + # Just use bash default auto-complete, because we never have two + # subcommands in the same line. If that is ever needed, change here to + # detect subsequent subcommands + COMPREPLY=($(compgen -o default -- "${COMP_WORDS[COMP_CWORD]}")) + return + fi +} + +function _gz_fuel_delete +{ + __get_comp_from_list "$GZ_DELETE_COMPLETION_LIST" +} + +function _gz_fuel_download +{ + __get_comp_from_list "$GZ_DOWNLOAD_COMPLETION_LIST" +} + +function _gz_fuel_edit +{ + __get_comp_from_list "$GZ_EDIT_COMPLETION_LIST" +} + +function _gz_fuel_list +{ + __get_comp_from_list "$GZ_LIST_COMPLETION_LIST" +} + +function _gz_fuel_meta +{ + __get_comp_from_list "$GZ_META_COMPLETION_LIST" +} + +function _gz_fuel_upload +{ + __get_comp_from_list "$GZ_UPLOAD_COMPLETION_LIST" +} + +# This searches the current list of typed words for one of the subcommands +# listed in GZ_FUEL_SUBCOMMANDS. This should work for most cases, but may fail +# if a word that looks like a subocmmand is used as an argument to a flag. Eg. +# `gz fuel --config download list`. Here `download` is an argument to +# `--config`, but this function will think that it's the subcommand. Since this +# seems like a rare scenario, we accept this failure mode. +function __get_subcommand +{ + local known_subcmd + local subcmd + for ((i=2; $i<=$COMP_CWORD; i++)); do + for subcmd in $GZ_FUEL_SUBCOMMANDS; do + if [[ "${COMP_WORDS[i]}" == "$subcmd" ]]; then + known_subcmd="$subcmd" + fi + done + done + echo "$known_subcmd" +} + +function _gz_fuel +{ + if [[ $COMP_CWORD > 2 ]]; then + local known_subcmd=$(__get_subcommand) + if [[ ! -z $known_subcmd ]]; then + local subcmd_func="_gz_fuel_$known_subcmd" + if [[ "$(type -t $subcmd_func)" == 'function' ]]; then + $subcmd_func + return + fi + fi + fi + + # If a subcommand is not found, assume we're still completing the subcommands + # or flags for `fuel`. + if [[ ${COMP_WORDS[COMP_CWORD]} == -* ]]; then + COMPREPLY=($(compgen -W "$GZ_FUEL_COMPLETION_LIST" \ + -- "${COMP_WORDS[COMP_CWORD]}" )) + else + COMPREPLY=($(compgen -W "${GZ_FUEL_SUBCOMMANDS}" -- ${cur})) + fi +} diff --git a/test/BUILD.bazel b/test/BUILD.bazel new file mode 100644 index 00000000..5a263079 --- /dev/null +++ b/test/BUILD.bazel @@ -0,0 +1,6 @@ +load( + "@gz//bazel/lint:lint.bzl", + "add_lint_tests", +) + +add_lint_tests()