diff --git a/.astylerc b/.astylerc new file mode 100644 index 0000000..a131157 --- /dev/null +++ b/.astylerc @@ -0,0 +1,8 @@ +-t +-j +-y +-c +-k1 +-z2 +-A2 +--pad-header diff --git a/.travis.yml b/.travis.yml index 9f7a3f4..cad7ff4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,5 +30,9 @@ before_script: script: # Run cmake, then compile and run tests with make - cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE .. && make -j$(nproc) && make test +after_failure: + # Dumps any logs so you can read the stderr + - if [[ -e 'Testing/Temporary/LastTest.log' ]]; then cat 'Testing/Temporary/LastTest.log'; fi + - if [[ -e 'CMakeFiles/CMakeError.log' ]]; then cat 'CMakeFiles/CMakeError.log'; fi notifications: email: false diff --git a/CMakeLists.txt b/CMakeLists.txt index 28a8297..e82cf99 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,25 +1,44 @@ -cmake_minimum_required(VERSION 3.0.0 FATAL_ERROR) +# For target_compile_features +cmake_minimum_required(VERSION 3.1.0 FATAL_ERROR) project(platform_folders VERSION 3.2.0 LANGUAGES CXX) # Since it's off, the library will be static by default option(BUILD_SHARED_LIBS "Build shared instead of static." OFF) -add_library(${PROJECT_NAME} +add_library(platform_folders sago/platform_folders.cpp ) -# Where to search for the header -# The BUILD/INSTALL interface expressions are for exporting -target_include_directories(${PROJECT_NAME} PRIVATE +# Defines standardized defaults for install paths +include(GNUInstallDirs) +# Where to search for the header while building +target_include_directories(platform_folders PUBLIC $ - $ + # Controls where #include starts to look from + # So /usr/include/ + # or C:\Program Files\platform_folders\include\ + $ ) # Define the header as public for installation -set_target_properties(${PROJECT_NAME} PROPERTIES +set_target_properties(platform_folders PROPERTIES PUBLIC_HEADER "sago/platform_folders.h" ) +# cxx_std_11 requires v3.8 +if(CMAKE_VERSION VERSION_LESS "3.8.0") + # Use old method of forcing C++11 + set(CMAKE_CXX_STANDARD 11) + set(CMAKE_CXX_STANDARD_REQUIRED true) +else() + # Require (minimum) C++11 when using header + # PRIVATE means only at compile time + target_compile_features(platform_folders PUBLIC cxx_std_11) +endif() + +# cxx_nullptr exists in v3.1 +target_compile_features(platform_folders PRIVATE cxx_nullptr) + # Apple requires linking to CoreServices # Check sys name instead of "APPLE" for cross-compilation if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") @@ -36,55 +55,56 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") endif() # Link to the CoreServices framework. This also sets the correct linking options # "If the library file is in a Mac OSX framework, the Headers directory of the framework will also be processed as a usage requirement." - target_link_libraries(${PROJECT_NAME} PRIVATE "${_CoreServices_FRAMEWORK}") + target_link_libraries(platform_folders PRIVATE "${_CoreServices_FRAMEWORK}") endif() -# Defines standardized defaults for install paths -include(GNUInstallDirs) -# For the config and configversion macros -include(CMakePackageConfigHelpers) - -# Controls what dir to prefix for the lib, so -set(_PROJECT_INSTALL_PREFIX_DIR "sago") -# Controls where the exports, config, and configversion files install to -set(_PROJECT_INSTALL_CMAKE_DIR "${CMAKE_INSTALL_FULL_LIBDIR}/cmake/${_PROJECT_INSTALL_PREFIX_DIR}") +# Cmake's find_package search path is different based on the system +# See https://cmake.org/cmake/help/latest/command/find_package.html for the list +if(CMAKE_SYSTEM_NAME STREQUAL "Windows") + # Controls where the exports, config, and configversion files install to + set(_PROJECT_INSTALL_CMAKE_DIR "${CMAKE_INSTALL_PREFIX}/cmake") +else() + # When calling find_package() + # it looks for /usr/lib/cmake//Config.cmake + set(_PROJECT_INSTALL_CMAKE_DIR "${CMAKE_INSTALL_LIBDIR}/cmake/platform_folders") +endif() # Gives "Make install" esque operations a location to install to... # and creates a .cmake file to be exported -install(TARGETS ${PROJECT_NAME} - EXPORT "${PROJECT_NAME}-targets" - LIBRARY DESTINATION "${CMAKE_INSTALL_FULL_LIBDIR}/${_PROJECT_INSTALL_PREFIX_DIR}" - ARCHIVE DESTINATION "${CMAKE_INSTALL_FULL_LIBDIR}/${_PROJECT_INSTALL_PREFIX_DIR}" - # Tells it where to put your headers if any set by set_target_properties - PUBLIC_HEADER DESTINATION "${CMAKE_INSTALL_FULL_INCLUDEDIR}/${_PROJECT_INSTALL_PREFIX_DIR}" - # Tells export where your includes folder is | Note that the private include path is not needed here - INCLUDES DESTINATION "${CMAKE_INSTALL_FULL_INCLUDEDIR}/${_PROJECT_INSTALL_PREFIX_DIR}" +install(TARGETS platform_folders + EXPORT "platform_folders-targets" + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" + ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" + # Tells it where to put the header files + PUBLIC_HEADER DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/sago" ) # "The install(TARGETS) and install(EXPORT) commands work together to install a target and a file to help import it" # Installs a cmake file which external projects can import. -install(EXPORT "${PROJECT_NAME}-targets" +install(EXPORT "platform_folders-targets" + NAMESPACE sago:: DESTINATION "${_PROJECT_INSTALL_CMAKE_DIR}" ) # "The export command is used to generate a file exporting targets from a project build tree" # Creates an import file for external projects which are aware of the build tree. # May be useful for cross-compiling -export(TARGETS ${PROJECT_NAME} - FILE "${PROJECT_NAME}-exports.cmake" +export(TARGETS platform_folders + FILE "platform_folders-exports.cmake" ) -configure_package_config_file("${PROJECT_NAME}Config.cmake.in" - "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" +# For the config and configversion macros +include(CMakePackageConfigHelpers) + +configure_package_config_file("platform_foldersConfig.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/platform_foldersConfig.cmake" # Tells the config file where it will be installed, so it can be correctly imported INSTALL_DESTINATION "${_PROJECT_INSTALL_CMAKE_DIR}" - # This passes the variables to the cmake.in file, which uses them - PATH_VARS PROJECT_NAME ) # Creates the project's ConfigVersion.cmake file # This allows for find_package() to use a version in the call -write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake" +write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/platform_foldersConfigVersion.cmake" # This'll require versioning in the project() call VERSION ${CMAKE_PROJECT_VERSION} # Just assuming Semver is followed @@ -93,8 +113,8 @@ write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Con # Install the ConfigVersion file, which is located in the build dir install(FILES - "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" - "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/platform_foldersConfig.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/platform_foldersConfigVersion.cmake" DESTINATION "${_PROJECT_INSTALL_CMAKE_DIR}" ) @@ -102,17 +122,5 @@ install(FILES include(CTest) # BUILD_TESTING is defined (default ON) in CTest if(BUILD_TESTING) - set(_PROJECT_TEST_NAME "${PROJECT_NAME}_test") - add_executable(${_PROJECT_TEST_NAME} platform_folders.cpp) - - target_link_libraries(${_PROJECT_TEST_NAME} PRIVATE ${PROJECT_NAME}) - - # Since tests aren't installed, no reason to give it an INSTALL_INTERFACE - target_include_directories(${_PROJECT_TEST_NAME} PRIVATE - $ - ) - - # Creates the "MyTest" test that runs the test executable created above - # This is triggered by things like "make test" - add_test(NAME MyTest COMMAND ${_PROJECT_TEST_NAME}) + add_subdirectory(test) endif() diff --git a/README.md b/README.md index ba1e811..05479d9 100644 --- a/README.md +++ b/README.md @@ -1,81 +1,117 @@ # PlatformFolders [![Build Status](https://travis-ci.org/sago007/PlatformFolders.svg?branch=master)](https://travis-ci.org/sago007/PlatformFolders) [![AppVeyor](https://img.shields.io/appveyor/ci/sago007/PlatformFolders.svg?label=Windows)](https://ci.appveyor.com/project/sago007/platformfolders) [![license](https://img.shields.io/github/license/sago007/PlatformFolders.svg)](https://raw.githubusercontent.com/sago007/PlatformFolders/master/LICENSE) [![Join the chat at https://gitter.im/PlatformFolders/Lobby](https://badges.gitter.im/PlatformFolders/Lobby.svg)](https://gitter.im/PlatformFolders/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/729e36adcf5c4523bd136de1b33441cb)](https://www.codacy.com/app/github_43/PlatformFolders?utm_source=github.com&utm_medium=referral&utm_content=sago007/PlatformFolders&utm_campaign=Badge_Grade) -A C++ library to look for special directories like "My Documents" and "%APPDATA%" so that you do not need to write Linux, Windows and Mac OS X specific code +A C++ library to look for directories like `My Documents`, `~/.config`, `%APPDATA%`, etc. so that you do not need to write platform-specific code -Can be found at: https://github.com/sago007/PlatformFolders +[Source code](https://github.com/sago007/PlatformFolders) • [Latest release](https://github.com/sago007/PlatformFolders/releases/latest) • [Doxygen documentation](http://sago007.github.io/PlatformFolders/html/doxygen/) -Releases can be downloaded here: https://github.com/sago007/PlatformFolders/releases +## Rationale -# Rationale There are a lot of platform abstraction libraries available. You can get graphics abstraction libraries, GUI abstraction libraries and file abstraction libraries. But folder abstraction seems to be more difficult. My problem was that the code that found the place to save data was platform dependent. This cluttered my code and often I would not discover that it did not compile until moving it to the different platforms. -I have written a bit more about it here: http://sago007.blogspot.dk/2015/10/abstraction-for-special-folders.html +[I have written a bit more about it here.](http://sago007.blogspot.dk/2015/10/abstraction-for-special-folders.html) There are some alternatives that you might consider instead: - * QStandardPaths - http://doc.qt.io/qt-5/qstandardpaths.html - * glib - https://developer.gnome.org/glib/stable/glib-Miscellaneous-Utility-Functions.html + +* [QStandardPaths](http://doc.qt.io/qt-5/qstandardpaths.html) +* [glib](https://developer.gnome.org/glib/stable/glib-Miscellaneous-Utility-Functions.html) Both are properly more mature than this library. However they are both parts of large frameworks and using them with libraries outside the framework may not be that simple. -# Documentation +## Operating System Support + +### Windows + +For Windows, the folders are fetched using SHGetFolderPath. + +The amount of supported folders differ from Windows version and this library targets XP and newer.\ +XP support will be dropped very soon. + +"Save Games" and "Downloads" should not be used, as they are undefined on XP. + +### Linux + +This library uses the [XDG user-dirs.](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html) + +It should work on any Unix system that has the following headers available: `pwd.h`, `unistd.h`, and `sys/types.h` + +### macOS + +Uses the deprecated FSFindFolder (there is no C++ alternative), which requires the CoreServices framework during linking. + +## Usage + +This project should be compatible with things like [Cmake's ExternalProject_Add](https://cmake.org/cmake/help/latest/module/ExternalProject.html?highlight=externalproject_add#command:externalproject_add) if you wish to use it in your project. + +You can also follow the [build step](#building) below to install at a system level, and use [Cmake's find_package](https://cmake.org/cmake/help/latest/command/find_package.html). + +```cmake +# Specifying a version is optional -- note it follows by Semver +find_package(platform_folders 3.1.0 REQUIRED) +# Which creates the IMPORTED lib "sago::platform_folders" +# Use it like so... +target_link_libraries(EXEORLIBNAME PRIVATE sago::platform_folders) +``` + +Alternatively, you can just copy the [sago](https://github.com/sago007/PlatformFolders/tree/master/sago) folder into your program and manually link everything. -Aside from this page there are also Doxygen available at http://sago007.github.io/PlatformFolders/html/doxygen/ +### Building -# Windows support -For Windows the folders are fetched using SHGetFolderPath. -The amount of supported folders differ from Windows version and this library targets XP and newer... and I'll drop XP support very soon. -Currently "Save Games" and "Downloads" should not be used as they are undefined on XP. +**Notes:** +* macOS requires the CoreServices framework during linking. +* If you don't want to install, remove the `--target install` command. -# Linux support -In Linux a lot of these folders are not official defined. However this library uses XDG user dirs. +Linux/macOS: -# Mac OS X support -Uses the deprecated FSFindFolder (there is no C++ alternative). It requires "-framework CoreServices" during linking. +``` +mkdir -p build && cd build +cmake -DBUILD_TESTING=OFF -DCMAKE_BUILD_TYPE=Release .. +sudo cmake --build . --target install +``` -# Usage -Copy "sago/platform_files.cpp" and "sago/platform_fildes.h" to your program and make sure that the cpp file is compiled and linked. +Windows: -It is also possible to compile and it like: ``` -mkdir -p build -cd build -cmake .. -make +mkdir build && cd build +cmake -DBUILD_TESTING=OFF .. +runas /user:Administrator "cmake --build . --config Release --target install" ``` -Just be aware that Mac OS X requires "-framework CoreServices" during linking no matter the choice. -# Hello World +## Example Usage This sample program gets all folders from the system: -``` -#include -#include -#include -#include "sago/platform_folders.h" -using std::cout; +```cpp +#include +#include +#include int main() { - cout << "Config: " << sago::getConfigHome() << "\n"; - cout << "Data: " << sago::getDataHome() << "\n"; - cout << "Cache: " << sago::getCacheDir() << "\n"; - cout << "Documents: " << sago::getDocumentsFolder() << "\n"; - cout << "Desktop: " << sago::getDesktopFolder() << "\n"; - cout << "Pictures: " << sago::getPicturesFolder() << "\n"; - cout << "Music: " << sago::getMusicFolder() << "\n"; - cout << "Video: " << sago::getVideoFolder() << "\n"; - cout << "Download: " << sago::getDownloadFolder1() << "\n"; - cout << "Save Games 1: " << sago::getSaveGamesFolder1() << "\n"; + using std::cout; + + cout << "Config: " << sago::getConfigHome() << '\n'; + cout << "Data: " << sago::getDataHome() << '\n'; + cout << "Cache: " << sago::getCacheDir() << '\n'; + sago::PlatformFolders p; + cout << "Documents: " << p.getDocumentsFolder() << "\n"; + cout << "Desktop: " << p.getDesktopFolder() << "\n"; + cout << "Pictures: " << p.getPicturesFolder() << "\n"; + cout << "Music: " << p.getMusicFolder() << "\n"; + cout << "Video: " << p.getVideoFolder() << "\n"; + cout << "Download: " << p.getDownloadFolder1() << "\n"; + cout << "Save Games 1: " << p.getSaveGamesFolder1() << "\n"; return 0; } ``` -The output on Linux would look like this: +### Example Output + +#### On Linux + ``` Config: /home/poul/.config Data: /home/poul/.local/share @@ -89,7 +125,8 @@ Download: /home/poul/Hentede filer Save Games 1: /home/poul/.local/share ``` -On Windows it could be: +#### On Windows + ``` Config: C:\users\poul\Application Data Data: C:\users\poul\Application Data @@ -103,7 +140,8 @@ Download: C:\users\poul\Skrivebord Save Games 1: C:\users\poul\Mine dokumenter\My Games ``` -On Mac OS X it could be: +#### On macOS + ``` Config: /Users/poul/Library/Application Support Data: /Users/poul/Library/Application Support @@ -117,16 +155,19 @@ Download: /Users/poul/Downloads Save Games 1: /Users/poul/Library/Application Support ``` -# C++ support -Versions up to 3.X.X should compile with any C++98 compiler. -Versions from 4.0.0 and up requires a C++11 compatible compiler. +## Compiler Compatibility + +Versions up to 3.X.X should compile with any C++98 compiler.\ +Versions from 4.0.0 and up require a C++11 compatible compiler. The aim is to always support the default C++ compiler on the oldest supported version of Ubuntu. This is a very basic library and it is not supposed to force you to upgrade. -# Encoding -From version 3.0 UTF-8 is always used on Windows and will also be the default on almost any other system. -Before version 3.0 Windows used ANSI encoding. Microsoft's default choice of UTF-16 is not compatible with platform independent code. -Although the user may use an characters they want I recommend, that the program should not have non ASCII characters in the source code itself. +## Encoding + +From version 3.0, Windows always encodes to UTF-8, and this will be the default on almost any other system. +Before version 3.0, Windows was encoded in ANSI. +Although the user may use any characters they want, I recommend that the program should have only ASCII characters in the source code itself. # Licence + Provided under the MIT license for the same reason XDG is licensed under it. So that you can quickly copy-paste the methods you need or just include the "sago"-folder. diff --git a/appveyor.yml b/appveyor.yml index 860cb79..6a6ce96 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -63,12 +63,13 @@ build_script: # to run your custom scripts instead of automatic tests # The %CONFIGURATION% and %PLATFORM% variables are set from Appveyor as env-vars test_script: - - cmd: MSBuild.exe /p:Configuration="%CONFIGURATION%" /p:Platform="%PLATFORM%" /v:minimal "%APPVEYOR_BUILD_FOLDER%"\build\RUN_TESTS.vcxproj + - cmd: IF "%PLATFORM%"=="x86" (MSBuild.exe "%APPVEYOR_BUILD_FOLDER%"\build\RUN_TESTS.vcxproj /p:Configuration="%CONFIGURATION%" /p:Platform="Win32" /v:minimal /m) ELSE (MSBuild.exe "%APPVEYOR_BUILD_FOLDER%"\build\RUN_TESTS.vcxproj /p:Configuration="%CONFIGURATION%" /p:Platform="%PLATFORM%" /v:minimal /m) #---------------------------------# # global handlers # #---------------------------------# -# on build failure dump cmake err log +# on build failure dump cmake and test err log on_failure: - cmd: IF exist "%APPVEYOR_BUILD_FOLDER%"\build\CMakeFiles\CMakeError.log (type "%APPVEYOR_BUILD_FOLDER%"\build\CMakeFiles\CMakeError.log) + - cmd: IF exist "%APPVEYOR_BUILD_FOLDER%"\build\Testing\Temporary\LastTest.log (type "%APPVEYOR_BUILD_FOLDER%"\build\Testing\Temporary\LastTest.log) diff --git a/platform_foldersConfig.cmake.in b/platform_foldersConfig.cmake.in index 6bd5004..f4a7964 100644 --- a/platform_foldersConfig.cmake.in +++ b/platform_foldersConfig.cmake.in @@ -1,3 +1,3 @@ @PACKAGE_INIT@ -include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@-targets.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/platform_folders-targets.cmake") diff --git a/sago/platform_folders.cpp b/sago/platform_folders.cpp index 34e6bae..c7b1709 100644 --- a/sago/platform_folders.cpp +++ b/sago/platform_folders.cpp @@ -29,22 +29,31 @@ SOFTWARE. #include "platform_folders.h" #include #include -#include #include #include -#if defined(_WIN32) +#ifdef _WIN32 +// Make sure we don't bring in all the extra junk with windows.h +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +// stringapiset.h depends on this #include +// For SUCCEEDED macro +#include +// For WideCharToMultiByte +#include +// For SHGetFolderPathW and various CSIDL "magic numbers" #include static std::string win32_utf16_to_utf8(const wchar_t* wstr) { std::string res; // If the 6th parameter is 0 then WideCharToMultiByte returns the number of bytes needed to store the result. - int actualSize = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL); + int actualSize = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, nullptr, 0, nullptr, nullptr); if (actualSize > 0) { //If the converted UTF-8 string could not be in the initial buffer. Allocate one that can hold it. std::vector buffer(actualSize); - actualSize = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, &buffer[0], buffer.size(), NULL, NULL); + actualSize = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, &buffer[0], buffer.size(), nullptr, nullptr); res = buffer.data(); } if (actualSize == 0) { @@ -58,7 +67,7 @@ static std::string win32_utf16_to_utf8(const wchar_t* wstr) { static std::string GetWindowsFolder(int folderId, const char* errorMsg) { wchar_t szPath[MAX_PATH]; szPath[0] = 0; - if ( !SUCCEEDED( SHGetFolderPathW( NULL, folderId, NULL, 0, szPath ) ) ) { + if ( !SUCCEEDED( SHGetFolderPathW( nullptr, folderId, nullptr, 0, szPath ) ) ) { throw std::runtime_error(errorMsg); } return win32_utf16_to_utf8(szPath); @@ -151,7 +160,7 @@ static std::string getLinuxFolderDefault(const char* envName, const char* defaul static void appendExtraFoldersTokenizer(const char* envName, const char* envValue, std::vector& folders) { std::vector buffer(envValue, envValue + std::strlen(envValue) + 1); char* p = std::strtok ( &buffer[0], ":"); - while (p != NULL) { + while (p != nullptr) { if (p[0] == '/') { folders.push_back(p); } @@ -160,7 +169,7 @@ static void appendExtraFoldersTokenizer(const char* envName, const char* envValu //The XDG documentation indicates that the folder should be ignored but that the program should continue. std::cerr << "Skipping path \"" << p << "\" in \"" << envName << "\" because it does not start with a \"/\"\n"; } - p = std::strtok (NULL, ":"); + p = std::strtok (nullptr, ":"); } } @@ -178,7 +187,7 @@ static void appendExtraFolders(const char* envName, const char* defaultValue, st namespace sago { std::string getDataHome() { -#if defined(_WIN32) +#ifdef _WIN32 return GetAppData(); #elif defined(__APPLE__) return GetMacFolder(kApplicationSupportFolderType, "Failed to find the Application Support Folder"); @@ -188,7 +197,7 @@ std::string getDataHome() { } std::string getConfigHome() { -#if defined(_WIN32) +#ifdef _WIN32 return GetAppData(); #elif defined(__APPLE__) return GetMacFolder(kApplicationSupportFolderType, "Failed to find the Application Support Folder"); @@ -198,7 +207,7 @@ std::string getConfigHome() { } std::string getCacheDir() { -#if defined(_WIN32) +#ifdef _WIN32 return GetAppDataLocal(); #elif defined(__APPLE__) return GetMacFolder(kCachedDataFolderType, "Failed to find the Application Support Folder"); @@ -208,26 +217,22 @@ std::string getCacheDir() { } void appendAdditionalDataDirectories(std::vector& homes) { -#if defined(_WIN32) +#ifdef _WIN32 homes.push_back(GetAppDataCommon()); -#elif defined(__APPLE__) -#else +#elif !defined(__APPLE__) appendExtraFolders("XDG_DATA_DIRS", "/usr/local/share/:/usr/share/", homes); #endif } void appendAdditionalConfigDirectories(std::vector& homes) { -#if defined(_WIN32) +#ifdef _WIN32 homes.push_back(GetAppDataCommon()); -#elif defined(__APPLE__) -#else +#elif !defined(__APPLE__) appendExtraFolders("XDG_CONFIG_DIRS", "/etc/xdg", homes); #endif } -#if defined(_WIN32) -#elif defined(__APPLE__) -#else +#if !defined(_WIN32) && !defined(__APPLE__) struct PlatformFolders::PlatformFoldersData { std::map folders; }; @@ -243,7 +248,6 @@ static void PlatformFoldersAddFromFile(const std::string& filename, std::map& folders) #endif PlatformFolders::PlatformFolders() { -#if defined(_WIN32) -#elif defined(__APPLE__) -#else +#if !defined(_WIN32) && !defined(__APPLE__) this->data = new PlatformFolders::PlatformFoldersData(); try { PlatformFoldersFillData(data->folders); @@ -282,15 +284,13 @@ PlatformFolders::PlatformFolders() { } PlatformFolders::~PlatformFolders() { -#if defined(_WIN32) -#elif defined(__APPLE__) -#else +#if !defined(_WIN32) && !defined(__APPLE__) delete this->data; #endif } std::string PlatformFolders::getDocumentsFolder() const { -#if defined(_WIN32) +#ifdef _WIN32 return GetWindowsFolder(CSIDL_PERSONAL, "Failed to find My Documents folder"); #elif defined(__APPLE__) return GetMacFolder(kDocumentsFolderType, "Failed to find Documents Folder"); @@ -300,7 +300,7 @@ std::string PlatformFolders::getDocumentsFolder() const { } std::string PlatformFolders::getDesktopFolder() const { -#if defined(_WIN32) +#ifdef _WIN32 return GetWindowsFolder(CSIDL_DESKTOP, "Failed to find Desktop folder"); #elif defined(__APPLE__) return GetMacFolder(kDesktopFolderType, "Failed to find Desktop folder"); @@ -310,7 +310,7 @@ std::string PlatformFolders::getDesktopFolder() const { } std::string PlatformFolders::getPicturesFolder() const { -#if defined(_WIN32) +#ifdef _WIN32 return GetWindowsFolder(CSIDL_MYPICTURES, "Failed to find My Pictures folder"); #elif defined(__APPLE__) return GetMacFolder(kPictureDocumentsFolderType, "Failed to find Picture folder"); @@ -320,7 +320,7 @@ std::string PlatformFolders::getPicturesFolder() const { } std::string PlatformFolders::getDownloadFolder1() const { -#if defined(_WIN32) +#ifdef _WIN32 //Pre Vista. Files was downloaded to the desktop return GetWindowsFolder(CSIDL_DESKTOP, "Failed to find My Downloads (Desktop) folder"); #elif defined(__APPLE__) @@ -331,7 +331,7 @@ std::string PlatformFolders::getDownloadFolder1() const { } std::string PlatformFolders::getMusicFolder() const { -#if defined(_WIN32) +#ifdef _WIN32 return GetWindowsFolder(CSIDL_MYMUSIC, "Failed to find My Music folder"); #elif defined(__APPLE__) return GetMacFolder(kMusicDocumentsFolderType, "Failed to find Music folder"); @@ -341,7 +341,7 @@ std::string PlatformFolders::getMusicFolder() const { } std::string PlatformFolders::getVideoFolder() const { -#if defined(_WIN32) +#ifdef _WIN32 return GetWindowsFolder(CSIDL_MYVIDEO, "Failed to find My Video folder"); #elif defined(__APPLE__) return GetMacFolder(kMovieDocumentsFolderType, "Failed to find Movie folder"); @@ -351,7 +351,7 @@ std::string PlatformFolders::getVideoFolder() const { } std::string PlatformFolders::getSaveGamesFolder1() const { -#if defined(_WIN32) +#ifdef _WIN32 //A dedicated Save Games folder was not introduced until Vista. For XP and older save games are most often saved in a normal folder named "My Games". //Data that should not be user accessible should be placed under GetDataHome() instead return GetWindowsFolder(CSIDL_PERSONAL, "Failed to find My Documents folder")+"\\My Games"; diff --git a/sago/platform_folders.h b/sago/platform_folders.h index bd6c0d5..193fa4d 100644 --- a/sago/platform_folders.h +++ b/sago/platform_folders.h @@ -218,9 +218,7 @@ public: private: PlatformFolders(const PlatformFolders&); PlatformFolders& operator=(const PlatformFolders&); -#if defined(_WIN32) -#elif defined(__APPLE__) -#else +#if !defined(_WIN32) && !defined(__APPLE__) struct PlatformFoldersData; PlatformFoldersData* data; #endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..8b11d44 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,50 @@ +# Create the tester lib +add_library(platformfolders_internal_tester + "tester.cpp" +) + +# cxx_std_11 requires v3.8 +if(CMAKE_VERSION VERSION_LESS "3.8.0") + # Use old method of forcing C++11 + set(CMAKE_CXX_STANDARD 11) + set(CMAKE_CXX_STANDARD_REQUIRED true) +else() + # Require (minimum) C++11 when using header + # PRIVATE means only at compile time + target_compile_features(platformfolders_internal_tester + PUBLIC cxx_std_11 + ) +endif() + +# cxx_range_for exists in v3.1 +target_compile_features(platformfolders_internal_tester + PRIVATE cxx_range_for +) + +target_include_directories(platformfolders_internal_tester + PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}" +) + +# Easily define a new test to run +macro(_def_test _name) + add_executable(${_name} "${_name}.cpp") + target_link_libraries(${_name} PRIVATE + platform_folders + platformfolders_internal_tester + ) + add_test(NAME "${_name}" COMMAND "${_name}") +endmacro() + +_def_test("appendAdditionalConfigDirectories") +_def_test("appendAdditionalDataDirectories") +_def_test("getCacheDir") +_def_test("getConfigHome") +_def_test("getDataHome") +_def_test("getDesktopFolder") +_def_test("getDocumentsFolder") +_def_test("getDownloadFolder1") +_def_test("getMusicFolder") +_def_test("getPicturesFolder") +_def_test("getSaveGamesFolder1") +_def_test("getVideoFolder") +_def_test("integration") diff --git a/test/appendAdditionalConfigDirectories.cpp b/test/appendAdditionalConfigDirectories.cpp new file mode 100644 index 0000000..d1e6c15 --- /dev/null +++ b/test/appendAdditionalConfigDirectories.cpp @@ -0,0 +1,11 @@ +#include "tester.hpp" +#include "../sago/platform_folders.h" +#include +#include + +int main() { + std::vector extraData; + sago::appendAdditionalConfigDirectories(extraData); + run_test(extraData); + return 0; +} diff --git a/test/appendAdditionalDataDirectories.cpp b/test/appendAdditionalDataDirectories.cpp new file mode 100644 index 0000000..ab83d22 --- /dev/null +++ b/test/appendAdditionalDataDirectories.cpp @@ -0,0 +1,11 @@ +#include "tester.hpp" +#include "../sago/platform_folders.h" +#include +#include + +int main() { + std::vector extraData; + sago::appendAdditionalDataDirectories(extraData); + run_test(extraData); + return 0; +} diff --git a/test/getCacheDir.cpp b/test/getCacheDir.cpp new file mode 100644 index 0000000..c7ae8d2 --- /dev/null +++ b/test/getCacheDir.cpp @@ -0,0 +1,7 @@ +#include "tester.hpp" +#include "../sago/platform_folders.h" + +int main() { + run_test(sago::getCacheDir()); + return 0; +} diff --git a/test/getConfigHome.cpp b/test/getConfigHome.cpp new file mode 100644 index 0000000..4253257 --- /dev/null +++ b/test/getConfigHome.cpp @@ -0,0 +1,7 @@ +#include "tester.hpp" +#include "../sago/platform_folders.h" + +int main() { + run_test(sago::getConfigHome()); + return 0; +} diff --git a/test/getDataHome.cpp b/test/getDataHome.cpp new file mode 100644 index 0000000..e7bd564 --- /dev/null +++ b/test/getDataHome.cpp @@ -0,0 +1,7 @@ +#include "tester.hpp" +#include "../sago/platform_folders.h" + +int main() { + run_test(sago::getDataHome()); + return 0; +} diff --git a/test/getDesktopFolder.cpp b/test/getDesktopFolder.cpp new file mode 100644 index 0000000..1851d67 --- /dev/null +++ b/test/getDesktopFolder.cpp @@ -0,0 +1,9 @@ +#include "tester.hpp" +#include "../sago/platform_folders.h" + +int main() { + run_test(sago::getDesktopFolder()); + sago::PlatformFolders p; + run_test(p.getDesktopFolder()); + return 0; +} diff --git a/test/getDocumentsFolder.cpp b/test/getDocumentsFolder.cpp new file mode 100644 index 0000000..02d4de8 --- /dev/null +++ b/test/getDocumentsFolder.cpp @@ -0,0 +1,9 @@ +#include "tester.hpp" +#include "../sago/platform_folders.h" + +int main() { + run_test(sago::getDocumentsFolder()); + sago::PlatformFolders p; + run_test(p.getDocumentsFolder()); + return 0; +} diff --git a/test/getDownloadFolder1.cpp b/test/getDownloadFolder1.cpp new file mode 100644 index 0000000..1bc9042 --- /dev/null +++ b/test/getDownloadFolder1.cpp @@ -0,0 +1,9 @@ +#include "tester.hpp" +#include "../sago/platform_folders.h" + +int main() { + run_test(sago::getDownloadFolder1()); + sago::PlatformFolders p; + run_test(p.getDownloadFolder1()); + return 0; +} diff --git a/test/getMusicFolder.cpp b/test/getMusicFolder.cpp new file mode 100644 index 0000000..946c353 --- /dev/null +++ b/test/getMusicFolder.cpp @@ -0,0 +1,9 @@ +#include "tester.hpp" +#include "../sago/platform_folders.h" + +int main() { + run_test(sago::getMusicFolder()); + sago::PlatformFolders p; + run_test(p.getMusicFolder()); + return 0; +} diff --git a/test/getPicturesFolder.cpp b/test/getPicturesFolder.cpp new file mode 100644 index 0000000..b1e5c09 --- /dev/null +++ b/test/getPicturesFolder.cpp @@ -0,0 +1,9 @@ +#include "tester.hpp" +#include "../sago/platform_folders.h" + +int main() { + run_test(sago::getPicturesFolder()); + sago::PlatformFolders p; + run_test(p.getPicturesFolder()); + return 0; +} diff --git a/test/getSaveGamesFolder1.cpp b/test/getSaveGamesFolder1.cpp new file mode 100644 index 0000000..4c68a6f --- /dev/null +++ b/test/getSaveGamesFolder1.cpp @@ -0,0 +1,9 @@ +#include "tester.hpp" +#include "../sago/platform_folders.h" + +int main() { + run_test(sago::getSaveGamesFolder1()); + sago::PlatformFolders p; + run_test(p.getSaveGamesFolder1()); + return 0; +} diff --git a/test/getVideoFolder.cpp b/test/getVideoFolder.cpp new file mode 100644 index 0000000..ec0be60 --- /dev/null +++ b/test/getVideoFolder.cpp @@ -0,0 +1,9 @@ +#include "tester.hpp" +#include "../sago/platform_folders.h" + +int main() { + run_test(sago::getVideoFolder()); + sago::PlatformFolders p; + run_test(p.getVideoFolder()); + return 0; +} diff --git a/test/integration.cpp b/test/integration.cpp new file mode 100644 index 0000000..74a456a --- /dev/null +++ b/test/integration.cpp @@ -0,0 +1,34 @@ +#include "tester.hpp" +#include "../sago/platform_folders.h" +#include +#include + +// This is all tests in one +int main() { + // Test plain functions + run_test(sago::getConfigHome()); + run_test(sago::getDataHome()); + run_test(sago::getCacheDir()); + // Test non-member functions + run_test(sago::getDesktopFolder()); + run_test(sago::getDocumentsFolder()); + run_test(sago::getDownloadFolder1()); + run_test(sago::getPicturesFolder()); + run_test(sago::getMusicFolder()); + run_test(sago::getVideoFolder()); + run_test(sago::getSaveGamesFolder1()); + // Test class methods + sago::PlatformFolders p; + run_test(p.getDocumentsFolder()); + run_test(p.getDesktopFolder()); + run_test(p.getPicturesFolder()); + run_test(p.getMusicFolder()); + run_test(p.getVideoFolder()); + run_test(p.getDownloadFolder1()); + run_test(p.getSaveGamesFolder1()); + // Test vector function + std::vector extraData; + sago::appendAdditionalDataDirectories(extraData); + run_test(extraData); + return 0; +} diff --git a/test/tester.cpp b/test/tester.cpp new file mode 100644 index 0000000..b90b4a9 --- /dev/null +++ b/test/tester.cpp @@ -0,0 +1,40 @@ +#include "tester.hpp" + +#include +#include +#include +#include +#include + +// This should be passed either be char* or std::string for this to work +static void test_internal(const std::string& data) { + try { + // Check that it actually got anything + if (data.empty()) { + throw std::logic_error("Got empty data"); + } + } + catch (const std::exception& e) { + // Take any standard exception & output its message + std::cerr << e.what() << std::endl; + std::exit(EXIT_FAILURE); + } + catch (...) { + // If any non-std exception is thrown, also fail + std::cerr << "Unknown exception!" << std::endl; + std::exit(EXIT_FAILURE); + } +} + +// std::string is expected input +void run_test(const std::string& input) { + test_internal(input); +} + +// A special overload for the two funcs that take a vector +void run_test(const std::vector& vec) { + // Check each value for validity + for (const std::string& elem : vec) { + test_internal(elem); + } +} diff --git a/test/tester.hpp b/test/tester.hpp new file mode 100644 index 0000000..65c8e0a --- /dev/null +++ b/test/tester.hpp @@ -0,0 +1,13 @@ +#ifndef SAGO_TEST_HPP +#define SAGO_TEST_HPP + +#include +#include + +// std::string is expected input +void run_test(const std::string&); + +// A special overload for the two funcs that take a vector +void run_test(const std::vector&); + +#endif