diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..aa95f7a --- /dev/null +++ b/.clang-format @@ -0,0 +1,108 @@ +--- +Language: Cpp +# BasedOnStyle: LLVM +AccessModifierOffset: -4 +AlignAfterOpenBracket: DontAlign +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Right +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: true +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: true +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: true + AfterControlStatement: true + AfterEnum: true + AfterFunction: true + AfterNamespace: false + AfterObjCDeclaration: true + AfterStruct: true + AfterUnion: true + BeforeCatch: true + BeforeElse: true + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Custom +BreakBeforeInheritanceComma: false +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: true +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 140 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + - Regex: '.*' + Priority: 1 +IncludeIsMainRegex: '(Test)?$' +IndentCaseLabels: false +IndentWidth: 4 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Right +ReflowComments: true +SortIncludes: false +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp11 +TabWidth: 8 +UseTab: Never +... + diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..309c7e9 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,28 @@ +Checks: 'modernize-*,modernize-use-override,google-*,-google-runtime-references,misc-*,clang-analyzer-*' +WarningsAsErrors: '' +HeaderFilterRegex: 'async.h|async_logger.h|common.h|details|formatter.h|logger.h|sinks|spdlog.h|tweakme.h|version.h' +AnalyzeTemporaryDtors: false +FormatStyle: none + +CheckOptions: + - key: google-readability-braces-around-statements.ShortStatementLines + value: '1' + - key: google-readability-function-size.StatementThreshold + value: '800' + - key: google-readability-namespace-comments.ShortNamespaceLines + value: '10' + - key: google-readability-namespace-comments.SpacesBeforeComments + value: '2' + - key: modernize-loop-convert.MaxCopySize + value: '16' + - key: modernize-loop-convert.MinConfidence + value: reasonable + - key: modernize-loop-convert.NamingStyle + value: CamelCase + - key: modernize-pass-by-value.IncludeStyle + value: llvm + - key: modernize-replace-auto-ptr.IncludeStyle + value: llvm + - key: modernize-use-nullptr.NullMacros + value: 'NULL' + diff --git a/.gitignore b/.gitignore index 5df9bd6..efb9396 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ -test-linux -test-win +build/ +.vscode/ +.DS_Store diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..02420f3 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,101 @@ +cmake_minimum_required(VERSION 3.13) + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") + +project(cfgpath LANGUAGES C CXX) + +include(string_utils) + +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/) + +if (MSVC) + add_compile_definitions(_CRT_SECURE_NO_WARNINGS) +endif () + +# Get version from cfgpath.core.h +file(READ include/cfgpath/cfgpath.core.h core_h) +if (NOT core_h MATCHES "CFGPATH_VERSION ([0-9]+)([0-9][0-9])([0-9][0-9])") + message(FATAL_ERROR "Cannot get CFGPATH_VERSION from core.h.") +endif () + +# Use math to skip leading zeros if any. +math(EXPR CPACK_PACKAGE_VERSION_MAJOR ${CMAKE_MATCH_1}) +math(EXPR CPACK_PACKAGE_VERSION_MINOR ${CMAKE_MATCH_2}) +math(EXPR CPACK_PACKAGE_VERSION_PATCH ${CMAKE_MATCH_3}) +join(CFGPATH_VERSION + ${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}) + +message(STATUS "Version: ${CFGPATH_VERSION}") +message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") + +include(CheckCXXCompilerFlag) + +# Generate 'compile_commands.json' for clang_complete +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +set(CXX_STD 14 CACHE STRING "Set to 11, 14, 17 or 20 to enable C++11, C++14, C++17 or C++20 builds, respectively." FORCE) +set(C_STD 11 CACHE STRING "Set to 99 or 11 to enable C99 or C11 builds, respectively." FORCE) + +if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") + if("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") + add_compile_options("-Wno-c++11-extensions" "-Wno-c++11-long-long" "-Wno-deprecated-declarations") + endif() +endif() + +option(CFGPATH_BUILD_TESTS "Build with tests enabled." ON) + +include_directories(${CMAKE_CURRENT_LIST_DIR}/include) + +# Define the cfgpath library, its includes and the needed defines. +file(GLOB CFGPATH_HEADER + ${CMAKE_CURRENT_LIST_DIR}/include/cfgpath/*.h + ${CMAKE_CURRENT_LIST_DIR}/include/cfgpath/detail/*.h + ${CMAKE_CURRENT_LIST_DIR}/include/cfgpath/detail/polyfills/*.h) + +file(GLOB CFGPATH_SRC + ${CMAKE_CURRENT_LIST_DIR}/src/*.c + ${CMAKE_CURRENT_LIST_DIR}/src/*.cpp) + +add_library(cfgpath ${CFGPATH_SRC} ${CFGPATH_HEADER} README.md) + +if(APPLE) + target_link_libraries(cfgpath "-framework Carbon") +endif() + +add_library(cfgpath::cfgpath ALIAS cfgpath) + +target_include_directories(cfgpath PUBLIC + $ + $) + +set_target_properties(cfgpath PROPERTIES + VERSION ${CFGPATH_VERSION} SOVERSION ${CPACK_PACKAGE_VERSION_MAJOR} + DEBUG_POSTFIX d) + +if (BUILD_SHARED_LIBS) + if (UNIX AND NOT APPLE) + # Fix rpmlint warning: + # unused-direct-shlib-dependency /usr/lib/libcfgpath.so.1.1.0 /lib/libm.so.6. + target_link_libraries(cfgpath -Wl,--as-needed) + endif () + target_compile_definitions(cfgpath PRIVATE CFGPATH_EXPORT INTERFACE CFGPATH_SHARED) +endif () + +add_library(cfgpath-header-only INTERFACE) +add_library(cfgpath::cfgpath-header-only ALIAS cfgpath-header-only) + +target_compile_definitions(cfgpath-header-only INTERFACE CFGPATH_HEADER_ONLY=1) + +target_include_directories(cfgpath-header-only INTERFACE + $ + $) + +if(CFGPATH_BUILD_TESTS) + enable_testing() + add_subdirectory(tests) +endif() + +# installer +include(CPack) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..cf1ab25 --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/Makefile b/Makefile deleted file mode 100644 index e1140a6..0000000 --- a/Makefile +++ /dev/null @@ -1,13 +0,0 @@ -.PHONY: all check - -all: test-linux test-win - -check: test-linux test-win - ./test-linux - ./test-win - -test-linux: test-linux.c cfgpath.h - $(CC) -O0 -g -o $@ $< - -test-win: test-win.c cfgpath.h shlobj.h - $(CC) -O0 -g -o $@ $< -I. diff --git a/README.md b/README.md index 2add710..0074a5f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ cfgpath.h: Cross platform methods for obtaining paths to configuration files ============================================================================ -Copyright 2013 Adam Nielsen + +Copyright (C) 2013 Adam Nielsen + +Copyright (C) 2019 LonghronShen --- @@ -15,24 +18,51 @@ purpose. If you add new platform support, please contribute a patch! Basic use: - #include "cfgpath.h" - - char cfgdir[MAX_PATH]; - get_user_config_file(cfgdir, sizeof(cfgdir), "myapp"); - if (cfgdir[0] == 0) { - printf("Unable to find home directory.\n"); - return 1; - } - printf("Saving configuration file to %s\n", cfgdir); +```C + +#include + +char cfgdir[MAX_PATH]; +get_user_config_file(cfgdir, sizeof(cfgdir), "myapp"); +if (cfgdir[0] == 0) +{ + printf("Unable to find home directory.\n"); + return 1; +} +printf("Saving configuration file to %s\n", cfgdir); + +``` +If you are using C++, you can also use it like this: + +```C++ + +#include +#include + +auto config_folder_path = libcfgpath::cfgpath::get_folder_path(libcfgpath::known_spefial_folder::CONFIG, "myapp"); + +if (config_folder_path == "") +{ + throw std::exception("Unable to find config directory."); +} + +``` + +To integrate it into your own project: + +* Just copy the **include** folder into your own project. + +* Or clone the project as a submodule of your project + 1. Add the **include** folder as an 'additional include directory' of your project; + + 2. If you are using CMake, you can use **add_subdirectory** to add the cloned submodule folder as an externl dependency, and link your project with the **cfgpath** library. Of course, you can also choose to use the **header only mode** by setting the **CFGPATH_HEADER_ONLY** variable. -To integrate it into your own project, just copy cfgpath.h. All the other -files are for testing to make sure it works correctly, so you don't need them -unless you intend to make changes and send me a patch. +All the other files are for testing to make sure it works correctly, so you don't need them unless you intend to make changes and send me a patch. Supported platforms are currently: - * Linux - * Mac OS X - * Windows +* Linux +* Mac OS X +* Windows Patches adding support for more platforms would be greatly appreciated. diff --git a/cfgpath.h b/cfgpath.h deleted file mode 100644 index b457235..0000000 --- a/cfgpath.h +++ /dev/null @@ -1,467 +0,0 @@ -/** - * @file cfgpath.h - * @brief Cross platform methods for obtaining paths to configuration files. - * - * Copyright (C) 2013 Adam Nielsen - * - * This code is placed in the public domain. You are free to use it for any - * purpose. If you add new platform support, please contribute a patch! - * - * Example use: - * - * char cfgdir[256]; - * get_user_config_file(cfgdir, sizeof(cfgdir), "myapp"); - * if (cfgdir[0] == 0) { - * printf("Unable to find home directory.\n"); - * return 1; - * } - * printf("Saving configuration file to %s\n", cfgdir); - * - * A number of constants are also defined: - * - * - MAX_PATH: Maximum length of a path, in characters. Used to allocate a - * char array large enough to hold the returned path. - * - * - PATH_SEPARATOR_CHAR: The separator between folders. This will be either a - * forward slash or a backslash depending on the platform. This is a - * character constant. - * - * - PATH_SEPARATOR_STRING: The same as PATH_SEPARATOR_CHAR but as a C string, - * to make it easier to append to other string constants. - */ - -#ifndef CFGPATH_H_ -#define CFGPATH_H_ - -#ifdef _MSC_VER -#define inline __inline -#include -#define mkdir _mkdir -#endif - -#ifdef __linux__ -#include -#include -#include -#define MAX_PATH 512 /* arbitrary value */ -#define PATH_SEPARATOR_CHAR '/' -#define PATH_SEPARATOR_STRING "/" -#elif defined(WIN32) -#include -/* MAX_PATH is defined by the Windows API */ -#define PATH_SEPARATOR_CHAR '\\' -#define PATH_SEPARATOR_STRING "\\" -#elif defined(__APPLE__) -#include -#include -#define MAX_PATH PATH_MAX -#define PATH_SEPARATOR_CHAR '/' -#define PATH_SEPARATOR_STRING "/" -#else -#error cfgpath.h functions have not been implemented for your platform! Please send patches. -#endif - -/** Get an absolute path to a single configuration file, specific to this user. - * - * This function is useful for programs that need only a single configuration - * file. The file is unique to the user account currently logged in. - * - * Output is typically: - * - * Windows: C:\Users\jcitizen\AppData\Roaming\appname.ini - * Linux: /home/jcitizen/.config/appname.conf - * Mac: /Users/jcitizen/Library/Application Support/appname.conf - * - * @param out - * Buffer to write the path. On return will contain the path, or an empty - * string on error. - * - * @param maxlen - * Length of out. Must be >= MAX_PATH. - * - * @param appname - * Short name of the application. Avoid using spaces or version numbers, and - * use lowercase if possible. - * - * @post The file may or may not exist. - * @post The folder holding the file is created if needed. - */ -static inline void get_user_config_file(char *out, unsigned int maxlen, const char *appname) -{ -#ifdef __linux__ - const char *out_orig = out; - char *home = getenv("XDG_CONFIG_HOME"); - unsigned int config_len = 0; - if (!home) { - home = getenv("HOME"); - if (!home) { - // Can't find home directory - out[0] = 0; - return; - } - config_len = strlen(".config/"); - } - - unsigned int home_len = strlen(home); - unsigned int appname_len = strlen(appname); - const int ext_len = strlen(".conf"); - - /* first +1 is "/", second is terminating null */ - if (home_len + 1 + config_len + appname_len + ext_len + 1 > maxlen) { - out[0] = 0; - return; - } - - memcpy(out, home, home_len); - out += home_len; - *out = '/'; - out++; - if (config_len) { - memcpy(out, ".config/", config_len); - out += config_len; - /* Make the .config folder if it doesn't already exist */ - mkdir(out_orig, 0755); - } - memcpy(out, appname, appname_len); - out += appname_len; - memcpy(out, ".conf", ext_len); - out += ext_len; - *out = 0; -#elif defined(WIN32) - if (maxlen < MAX_PATH) { - out[0] = 0; - return; - } - if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, out))) { - out[0] = 0; - return; - } - /* We don't try to create the AppData folder as it always exists already */ - unsigned int appname_len = strlen(appname); - if (strlen(out) + 1 + appname_len + strlen(".ini") + 1 > maxlen) { - out[0] = 0; - return; - } - strcat(out, "\\"); - strcat(out, appname); - strcat(out, ".ini"); -#elif defined(__APPLE__) - FSRef ref; - FSFindFolder(kUserDomain, kApplicationSupportFolderType, kCreateFolder, &ref); - char home[MAX_PATH]; - FSRefMakePath(&ref, (UInt8 *)&home, MAX_PATH); - /* first +1 is "/", second is terminating null */ - const char *ext = ".conf"; - if (strlen(home) + 1 + strlen(appname) + strlen(ext) + 1 > maxlen) { - out[0] = 0; - return; - } - - strcpy(out, home); - strcat(out, PATH_SEPARATOR_STRING); - strcat(out, appname); - strcat(out, ext); -#endif -} - -/** Get an absolute path to a configuration folder, specific to this user. - * - * This function is useful for programs that need to store multiple - * configuration files. The output is a folder (which may not exist and will - * need to be created) suitable for storing a number of files. - * - * The returned path will always end in a platform-specific trailing slash, so - * that a filename can simply be appended to the path. - * - * Output is typically: - * - * Windows: C:\Users\jcitizen\AppData\Roaming\appname\ - * Linux: /home/jcitizen/.config/appname/ - * Mac: /Users/jcitizen/Library/Application Support/appname/ - * - * @param out - * Buffer to write the path. On return will contain the path, or an empty - * string on error. - * - * @param maxlen - * Length of out. Must be >= MAX_PATH. - * - * @param appname - * Short name of the application. Avoid using spaces or version numbers, and - * use lowercase if possible. - * - * @post The folder is created if needed. - */ -static inline void get_user_config_folder(char *out, unsigned int maxlen, const char *appname) -{ -#ifdef __linux__ - const char *out_orig = out; - char *home = getenv("XDG_CONFIG_HOME"); - unsigned int config_len = 0; - if (!home) { - home = getenv("HOME"); - if (!home) { - // Can't find home directory - out[0] = 0; - return; - } - config_len = strlen(".config/"); - } - - unsigned int home_len = strlen(home); - unsigned int appname_len = strlen(appname); - - /* first +1 is "/", second is trailing "/", third is terminating null */ - if (home_len + 1 + config_len + appname_len + 1 + 1 > maxlen) { - out[0] = 0; - return; - } - - memcpy(out, home, home_len); - out += home_len; - *out = '/'; - out++; - if (config_len) { - memcpy(out, ".config/", config_len); - out += config_len; - /* Make the .config folder if it doesn't already exist */ - *out = '\0'; - mkdir(out_orig, 0755); - } - memcpy(out, appname, appname_len); - out += appname_len; - /* Make the .config/appname folder if it doesn't already exist */ - *out = '\0'; - mkdir(out_orig, 0755); - *out = '/'; - out++; - *out = 0; -#elif defined(WIN32) - if (maxlen < MAX_PATH) { - out[0] = 0; - return; - } - if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, out))) { - out[0] = 0; - return; - } - /* We don't try to create the AppData folder as it always exists already */ - unsigned int appname_len = strlen(appname); - if (strlen(out) + 1 + appname_len + 1 + 1 > maxlen) { - out[0] = 0; - return; - } - strcat(out, "\\"); - strcat(out, appname); - /* Make the AppData\appname folder if it doesn't already exist */ - mkdir(out); - strcat(out, "\\"); -#elif defined(__APPLE__) - FSRef ref; - FSFindFolder(kUserDomain, kApplicationSupportFolderType, kCreateFolder, &ref); - char home[MAX_PATH]; - FSRefMakePath(&ref, (UInt8 *)&home, MAX_PATH); - /* first +1 is "/", second is trailing "/", third is terminating null */ - if (strlen(home) + 1 + strlen(appname) + 1 + 1 > maxlen) { - out[0] = 0; - return; - } - - strcpy(out, home); - strcat(out, PATH_SEPARATOR_STRING); - strcat(out, appname); - /* Make the .config/appname folder if it doesn't already exist */ - mkdir(out, 0755); - strcat(out, PATH_SEPARATOR_STRING); -#endif -} - -/** Get an absolute path to a data storage folder, specific to this user. - * - * This function is useful for programs that need to store larger amounts of - * data specific to each user. The output is a folder (which may not exist and - * will need to be created) suitable for storing a number of data files. - * - * This path should be used for persistent, important data files the user would - * want to keep. Do not use this path for temporary files, cache files, or - * other files that can be recreated if they are deleted. Use - * get_user_cache_folder() for those instead. - * - * The returned path will always end in a platform-specific trailing slash, so - * that a filename can simply be appended to the path. - * - * Output is typically: - * - * Windows: C:\Users\jcitizen\AppData\Roaming\appname-data\ - * Linux: /home/jcitizen/.local/share/appname/ - * Mac: /Users/jcitizen/Library/Application Support/appname-data/ - * - * @param out - * Buffer to write the path. On return will contain the path, or an empty - * string on error. - * - * @param maxlen - * Length of out. Must be >= MAX_PATH. - * - * @param appname - * Short name of the application. Avoid using spaces or version numbers, and - * use lowercase if possible. - * - * @post The folder is created if needed. - */ -static inline void get_user_data_folder(char *out, unsigned int maxlen, const char *appname) -{ -#ifdef __linux__ - const char *out_orig = out; - char *home = getenv("XDG_DATA_HOME"); - unsigned int config_len = 0; - if (!home) { - home = getenv("HOME"); - if (!home) { - // Can't find home directory - out[0] = 0; - return; - } - config_len = strlen(".local/share/"); - } - - unsigned int home_len = strlen(home); - unsigned int appname_len = strlen(appname); - - /* first +1 is "/", second is trailing "/", third is terminating null */ - if (home_len + 1 + config_len + appname_len + 1 + 1 > maxlen) { - out[0] = 0; - return; - } - - memcpy(out, home, home_len); - out += home_len; - *out = '/'; - out++; - if (config_len) { - memcpy(out, ".local/share/", config_len); - out += config_len; - /* Make the .local/share folder if it doesn't already exist */ - *out = '\0'; - mkdir(out_orig, 0755); - } - memcpy(out, appname, appname_len); - out += appname_len; - /* Make the .local/share/appname folder if it doesn't already exist */ - *out = '\0'; - mkdir(out_orig, 0755); - *out = '/'; - out++; - *out = 0; -#elif defined(WIN32) || defined(__APPLE__) - /* No distinction under Windows or OS X */ - get_user_config_folder(out, maxlen, appname); -#endif -} - -/** Get an absolute path to a temporary storage folder, specific to this user. - * - * This function is useful for programs that temporarily need to store larger - * amounts of data specific to each user. The output is a folder (which may - * not exist and will need to be created) suitable for storing a number of - * temporary files. The files may be lost or deleted when the program - * terminates. - * - * This path should be used for temporary, unimportant data files that can - * safely be deleted after the program terminates. Do not use this path for - * any important files the user would want to keep. Use get_user_data_folder() - * for those instead. - * - * The returned path will always end in a platform-specific trailing slash, so - * that a filename can simply be appended to the path. - * - * Output is typically: - * - * Windows: C:\Users\jcitizen\AppData\Local\appname\ - * Linux: /home/jcitizen/.cache/appname/ - * Mac: /Users/jcitizen/Library/Application Support/appname/ - * - * @param out - * Buffer to write the path. On return will contain the path, or an empty - * string on error. - * - * @param maxlen - * Length of out. Must be >= MAX_PATH. - * - * @param appname - * Short name of the application. Avoid using spaces or version numbers, and - * use lowercase if possible. - * - * @post The folder is created if needed. - */ -static inline void get_user_cache_folder(char *out, unsigned int maxlen, const char *appname) -{ -#ifdef __linux__ - const char *out_orig = out; - char *home = getenv("XDG_CACHE_HOME"); - unsigned int config_len = 0; - if (!home) { - home = getenv("HOME"); - if (!home) { - // Can't find home directory - out[0] = 0; - return; - } - config_len = strlen(".cache/"); - } - - unsigned int home_len = strlen(home); - unsigned int appname_len = strlen(appname); - - /* first +1 is "/", second is trailing "/", third is terminating null */ - if (home_len + 1 + config_len + appname_len + 1 + 1 > maxlen) { - out[0] = 0; - return; - } - - memcpy(out, home, home_len); - out += home_len; - *out = '/'; - out++; - if (config_len) { - memcpy(out, ".cache/", config_len); - out += config_len; - /* Make the .cache folder if it doesn't already exist */ - *out = '\0'; - mkdir(out_orig, 0755); - } - memcpy(out, appname, appname_len); - out += appname_len; - /* Make the .cache/appname folder if it doesn't already exist */ - *out = '\0'; - mkdir(out_orig, 0755); - *out = '/'; - out++; - *out = 0; -#elif defined(WIN32) - if (maxlen < MAX_PATH) { - out[0] = 0; - return; - } - if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, out))) { - out[0] = 0; - return; - } - /* We don't try to create the AppData folder as it always exists already */ - unsigned int appname_len = strlen(appname); - if (strlen(out) + 1 + appname_len + 1 + 1 > maxlen) { - out[0] = 0; - return; - } - strcat(out, "\\"); - strcat(out, appname); - /* Make the AppData\appname folder if it doesn't already exist */ - mkdir(out); - strcat(out, "\\"); -#elif defined(__APPLE__) - /* No distinction under OS X */ - get_user_config_folder(out, maxlen, appname); -#endif -} - -#endif /* CFGPATH_H_ */ diff --git a/clang_tidy.sh b/clang_tidy.sh new file mode 100755 index 0000000..ff32b95 --- /dev/null +++ b/clang_tidy.sh @@ -0,0 +1,3 @@ +#!/bin/bash +echo -n "Running clang-tidy " +find . -name "*\.h" -o -name "*\.cpp" -o -name "*\.c"|grep -v bundled|xargs -I {} sh -c "clang-tidy '{}' -- -I ./include 2>/dev/null; echo -n '.'" \ No newline at end of file diff --git a/cmake/string_utils.cmake b/cmake/string_utils.cmake new file mode 100644 index 0000000..891eaaa --- /dev/null +++ b/cmake/string_utils.cmake @@ -0,0 +1,8 @@ +# Joins arguments and places the results in ${result_var}. +function(join result_var) + set(result ) + foreach (arg ${ARGN}) + set(result "${result}${arg}") + endforeach () + set(${result_var} "${result}" PARENT_SCOPE) +endfunction() \ No newline at end of file diff --git a/format.sh b/format.sh new file mode 100755 index 0000000..7579b85 --- /dev/null +++ b/format.sh @@ -0,0 +1,9 @@ +#!/bin/bash +echo -n "Running dos2unix " +find . -name "*\.h" -o -name "*\.cpp" -o -name "*\.c"|grep -v bundled|xargs -I {} sh -c "dos2unix '{}' 2>/dev/null; echo -n '.'" +echo +echo -n "Running clang-format " +find . -name "*\.h" -o -name "*\.cpp" -o -name "*\.c"|grep -v bundled|xargs -I {} sh -c "clang-format -i {}; echo -n '.'" +echo + + diff --git a/include/cfgpath/cfgpath.core.h b/include/cfgpath/cfgpath.core.h new file mode 100644 index 0000000..ca04e84 --- /dev/null +++ b/include/cfgpath/cfgpath.core.h @@ -0,0 +1,48 @@ +/** + * @file cfgpath.core.h + * @brief Cross platform methods for obtaining paths to configuration files. + * + * Copyright (C) 2013 Adam Nielsen + * Copyright (C) 2019 LonghronShen + * + * This code is placed in the public domain. You are free to use it for any + * purpose. If you add new platform support, please contribute a patch! + */ + +#ifndef CFGPATH_CORE_H +#define CFGPATH_CORE_H + +#ifdef _MSC_VER +#if _MSC_VER > 1000 +#pragma once +#endif +#endif + +// The cfgpath library version in the form major * 10000 + minor * 100 + patch. +#define CFGPATH_VERSION 10000 + +# if defined(CFGPATH_HEADER_ONLY) +# if defined(CFGPATH_IMPL) +# define CFGPATH_FUNC +# else +# define CFGPATH_FUNC static inline +# endif +# if defined(_WIN32) || defined(WIN32) || defined(WIN64) +# include +# else +# include +# if defined(__linux__) +# include +# elif defined(__APPLE__) +# include +# else +# error cfgpath.h functions have not been implemented for your platform! Please send patches. +# endif +# endif +# else +# include +# endif + +#include + +#endif diff --git a/include/cfgpath/cfgpath.h b/include/cfgpath/cfgpath.h new file mode 100644 index 0000000..2814c2f --- /dev/null +++ b/include/cfgpath/cfgpath.h @@ -0,0 +1,71 @@ +/** + * @file cfgpath.incl.h + * @brief Cross platform methods for obtaining paths to configuration files. + * + * Copyright (C) 2013 Adam Nielsen + * Copyright (C) 2019 LonghronShen + * + * This code is placed in the public domain. You are free to use it for any + * purpose. If you add new platform support, please contribute a patch! + * + * Example use: + * + * char cfgdir[256]; + * get_user_config_file(cfgdir, sizeof(cfgdir), "myapp"); + * if (cfgdir[0] == 0) { + * printf("Unable to find home directory.\n"); + * return 1; + * } + * printf("Saving configuration file to %s\n", cfgdir); + * + * A number of constants are also defined: + * + * - MAX_PATH: Maximum length of a path, in characters. Used to allocate a + * char array large enough to hold the returned path. + * + * - PATH_SEPARATOR_CHAR: The separator between folders. This will be either a + * forward slash or a backslash depending on the platform. This is a + * character constant. + * + * - PATH_SEPARATOR_STRING: The same as PATH_SEPARATOR_CHAR but as a C string, + * to make it easier to append to other string constants. + */ + +#ifndef CFGPATH_H +#define CFGPATH_H + +#ifdef _MSC_VER +#if _MSC_VER > 1000 +#pragma once +#endif +#endif + +#include + +#ifdef __cplusplus + +#include + +namespace libcfgpath { + +enum known_spefial_folder { + CONFIG, + DATA, + CACHE, +}; + +class cfgpath { +public: + static std::string get_folder_path(libcfgpath::known_spefial_folder folder, + const std::string &appname); + + static std::string get_file_path(libcfgpath::known_spefial_folder folder, + const std::string &appname, + const std::string &fileName); +}; + +} // namespace libcfgpath + +#endif + +#endif \ No newline at end of file diff --git a/include/cfgpath/cfgpath.incl.h b/include/cfgpath/cfgpath.incl.h new file mode 100644 index 0000000..eaad0e9 --- /dev/null +++ b/include/cfgpath/cfgpath.incl.h @@ -0,0 +1,158 @@ +/** + * @file cfgpath.incl.h + * @brief Cross platform methods for obtaining paths to configuration files. + * + * Copyright (C) 2013 Adam Nielsen + * Copyright (C) 2019 LonghronShen + * + * This code is placed in the public domain. You are free to use it for any + * purpose. If you add new platform support, please contribute a patch! + */ + +#ifndef CFGPATH_INCL_H +#define CFGPATH_INCL_H + +#ifdef _MSC_VER +#if _MSC_VER > 1000 +#pragma once +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/** Get an absolute path to a single configuration file, specific to this user. + * + * This function is useful for programs that need only a single configuration + * file. The file is unique to the user account currently logged in. + * + * Output is typically: + * + * Windows: C:\Users\jcitizen\AppData\Roaming\appname.ini + * Linux: /home/jcitizen/.config/appname.conf + * Mac: /Users/jcitizen/Library/Application Support/appname.conf + * + * @param out + * Buffer to write the path. On return will contain the path, or an empty + * string on error. + * + * @param maxlen + * Length of out. Must be >= MAX_PATH. + * + * @param appname + * Short name of the application. Avoid using spaces or version numbers, and + * use lowercase if possible. + * + * @post The file may or may not exist. + * @post The folder holding the file is created if needed. + */ +void get_user_config_file(char *out, unsigned int maxlen, const char *appname); + +/** Get an absolute path to a configuration folder, specific to this user. + * + * This function is useful for programs that need to store multiple + * configuration files. The output is a folder (which may not exist and will + * need to be created) suitable for storing a number of files. + * + * The returned path will always end in a platform-specific trailing slash, so + * that a filename can simply be appended to the path. + * + * Output is typically: + * + * Windows: C:\Users\jcitizen\AppData\Roaming\appname\ + * Linux: /home/jcitizen/.config/appname/ + * Mac: /Users/jcitizen/Library/Application Support/appname/ + * + * @param out + * Buffer to write the path. On return will contain the path, or an empty + * string on error. + * + * @param maxlen + * Length of out. Must be >= MAX_PATH. + * + * @param appname + * Short name of the application. Avoid using spaces or version numbers, and + * use lowercase if possible. + * + * @post The folder is created if needed. + */ +void get_user_config_folder(char *out, unsigned int maxlen, const char *appname); + +/** Get an absolute path to a data storage folder, specific to this user. + * + * This function is useful for programs that need to store larger amounts of + * data specific to each user. The output is a folder (which may not exist and + * will need to be created) suitable for storing a number of data files. + * + * This path should be used for persistent, important data files the user would + * want to keep. Do not use this path for temporary files, cache files, or + * other files that can be recreated if they are deleted. Use + * get_user_cache_folder() for those instead. + * + * The returned path will always end in a platform-specific trailing slash, so + * that a filename can simply be appended to the path. + * + * Output is typically: + * + * Windows: C:\Users\jcitizen\AppData\Roaming\appname-data\ + * Linux: /home/jcitizen/.local/share/appname/ + * Mac: /Users/jcitizen/Library/Application Support/appname-data/ + * + * @param out + * Buffer to write the path. On return will contain the path, or an empty + * string on error. + * + * @param maxlen + * Length of out. Must be >= MAX_PATH. + * + * @param appname + * Short name of the application. Avoid using spaces or version numbers, and + * use lowercase if possible. + * + * @post The folder is created if needed. + */ +void get_user_data_folder(char *out, unsigned int maxlen, const char *appname); + +/** Get an absolute path to a temporary storage folder, specific to this user. + * + * This function is useful for programs that temporarily need to store larger + * amounts of data specific to each user. The output is a folder (which may + * not exist and will need to be created) suitable for storing a number of + * temporary files. The files may be lost or deleted when the program + * terminates. + * + * This path should be used for temporary, unimportant data files that can + * safely be deleted after the program terminates. Do not use this path for + * any important files the user would want to keep. Use get_user_data_folder() + * for those instead. + * + * The returned path will always end in a platform-specific trailing slash, so + * that a filename can simply be appended to the path. + * + * Output is typically: + * + * Windows: C:\Users\jcitizen\AppData\Local\appname\ + * Linux: /home/jcitizen/.cache/appname/ + * Mac: /Users/jcitizen/Library/Application Support/appname/ + * + * @param out + * Buffer to write the path. On return will contain the path, or an empty + * string on error. + * + * @param maxlen + * Length of out. Must be >= MAX_PATH. + * + * @param appname + * Short name of the application. Avoid using spaces or version numbers, and + * use lowercase if possible. + * + * @post The folder is created if needed. + */ +void get_user_cache_folder(char *out, unsigned int maxlen, const char *appname); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/include/cfgpath/detail/cfgpath.impl.linux.h b/include/cfgpath/detail/cfgpath.impl.linux.h new file mode 100644 index 0000000..56d7008 --- /dev/null +++ b/include/cfgpath/detail/cfgpath.impl.linux.h @@ -0,0 +1,234 @@ +/** + * @file cfgpath.impl.linux.h + * @brief Cross platform methods for obtaining paths to configuration files. + * + * Copyright (C) 2013 Adam Nielsen + * Copyright (C) 2019 LonghronShen + * + * This code is placed in the public domain. You are free to use it for any + * purpose. If you add new platform support, please contribute a patch! + */ + +#if __linux__ || defined(__linux__) + +#ifndef CFGPATH_LINUX_H +#define CFGPATH_LINUX_H + +#if defined(CFGPATH_HEADER_ONLY) +#if defined(CFGPATH_IMPL) +#define CFGPATH_FUNC +#else +#define CFGPATH_FUNC static inline +#endif +#else +#define CFGPATH_FUNC +#endif + +#include +#include +#include + +#ifdef MAX_PATH +#undef MAX_PATH +#endif + +#define MAX_PATH 512 /* arbitrary value */ +#define PATH_SEPARATOR_CHAR '/' +#define PATH_SEPARATOR_STRING "/" + +CFGPATH_FUNC void get_user_config_file(char *out, unsigned int maxlen, const char *appname) +{ + const char *out_orig = out; + char *home = getenv("XDG_CONFIG_HOME"); + unsigned int config_len = 0; + if (!home) + { + home = getenv("HOME"); + if (!home) + { + // Can't find home directory + out[0] = 0; + return; + } + config_len = strlen(".config/"); + } + + unsigned int home_len = strlen(home); + unsigned int appname_len = strlen(appname); + const int ext_len = strlen(".conf"); + + /* first +1 is "/", second is terminating null */ + if (home_len + 1 + config_len + appname_len + ext_len + 1 > maxlen) + { + out[0] = 0; + return; + } + + memcpy(out, home, home_len); + out += home_len; + *out = '/'; + out++; + if (config_len) + { + memcpy(out, ".config/", config_len); + out += config_len; + /* Make the .config folder if it doesn't already exist */ + mkdir(out_orig, 0755); + } + memcpy(out, appname, appname_len); + out += appname_len; + memcpy(out, ".conf", ext_len); + out += ext_len; + *out = 0; +} + +CFGPATH_FUNC void get_user_config_folder(char *out, unsigned int maxlen, const char *appname) +{ + const char *out_orig = out; + char *home = getenv("XDG_CONFIG_HOME"); + unsigned int config_len = 0; + if (!home) + { + home = getenv("HOME"); + if (!home) + { + // Can't find home directory + out[0] = 0; + return; + } + config_len = strlen(".config/"); + } + + unsigned int home_len = strlen(home); + unsigned int appname_len = strlen(appname); + + /* first +1 is "/", second is trailing "/", third is terminating null */ + if (home_len + 1 + config_len + appname_len + 1 + 1 > maxlen) + { + out[0] = 0; + return; + } + + memcpy(out, home, home_len); + out += home_len; + *out = '/'; + out++; + if (config_len) + { + memcpy(out, ".config/", config_len); + out += config_len; + /* Make the .config folder if it doesn't already exist */ + *out = '\0'; + mkdir(out_orig, 0755); + } + memcpy(out, appname, appname_len); + out += appname_len; + /* Make the .config/appname folder if it doesn't already exist */ + *out = '\0'; + mkdir(out_orig, 0755); + *out = '/'; + out++; + *out = 0; +} + +CFGPATH_FUNC void get_user_data_folder(char *out, unsigned int maxlen, const char *appname) +{ + const char *out_orig = out; + char *home = getenv("XDG_DATA_HOME"); + unsigned int config_len = 0; + if (!home) + { + home = getenv("HOME"); + if (!home) + { + // Can't find home directory + out[0] = 0; + return; + } + config_len = strlen(".local/share/"); + } + + unsigned int home_len = strlen(home); + unsigned int appname_len = strlen(appname); + + /* first +1 is "/", second is trailing "/", third is terminating null */ + if (home_len + 1 + config_len + appname_len + 1 + 1 > maxlen) + { + out[0] = 0; + return; + } + + memcpy(out, home, home_len); + out += home_len; + *out = '/'; + out++; + if (config_len) + { + memcpy(out, ".local/share/", config_len); + out += config_len; + /* Make the .local/share folder if it doesn't already exist */ + *out = '\0'; + mkdir(out_orig, 0755); + } + memcpy(out, appname, appname_len); + out += appname_len; + /* Make the .local/share/appname folder if it doesn't already exist */ + *out = '\0'; + mkdir(out_orig, 0755); + *out = '/'; + out++; + *out = 0; +} + +CFGPATH_FUNC void get_user_cache_folder(char *out, unsigned int maxlen, const char *appname) +{ + const char *out_orig = out; + char *home = getenv("XDG_CACHE_HOME"); + unsigned int config_len = 0; + if (!home) + { + home = getenv("HOME"); + if (!home) + { + // Can't find home directory + out[0] = 0; + return; + } + config_len = strlen(".cache/"); + } + + unsigned int home_len = strlen(home); + unsigned int appname_len = strlen(appname); + + /* first +1 is "/", second is trailing "/", third is terminating null */ + if (home_len + 1 + config_len + appname_len + 1 + 1 > maxlen) + { + out[0] = 0; + return; + } + + memcpy(out, home, home_len); + out += home_len; + *out = '/'; + out++; + if (config_len) + { + memcpy(out, ".cache/", config_len); + out += config_len; + /* Make the .cache folder if it doesn't already exist */ + *out = '\0'; + mkdir(out_orig, 0755); + } + memcpy(out, appname, appname_len); + out += appname_len; + /* Make the .cache/appname folder if it doesn't already exist */ + *out = '\0'; + mkdir(out_orig, 0755); + *out = '/'; + out++; + *out = 0; +} + +#endif + +#endif \ No newline at end of file diff --git a/include/cfgpath/detail/cfgpath.impl.osx.h b/include/cfgpath/detail/cfgpath.impl.osx.h new file mode 100644 index 0000000..e3a98e9 --- /dev/null +++ b/include/cfgpath/detail/cfgpath.impl.osx.h @@ -0,0 +1,93 @@ +/** + * @file cfgpath.impl.osx.h + * @brief Cross platform methods for obtaining paths to configuration files. + * + * Copyright (C) 2013 Adam Nielsen + * Copyright (C) 2019 LonghronShen + * + * This code is placed in the public domain. You are free to use it for any + * purpose. If you add new platform support, please contribute a patch! + */ + +#if __APPLE__ || defined(__APPLE__) + +#ifndef CFGPATH_OSX_H +#define CFGPATH_OSX_H + +#if defined(CFGPATH_HEADER_ONLY) +#if defined(CFGPATH_IMPL) +#define CFGPATH_FUNC +#else +#define CFGPATH_FUNC static inline +#endif +#else +#define CFGPATH_FUNC +#endif + +#include +#include + +#ifdef MAX_PATH +#undef MAX_PATH +#endif + +#define MAX_PATH PATH_MAX +#define PATH_SEPARATOR_CHAR '/' +#define PATH_SEPARATOR_STRING "/" + +CFGPATH_FUNC void get_user_config_file(char *out, unsigned int maxlen, const char *appname) +{ + FSRef ref; + FSFindFolder(kUserDomain, kApplicationSupportFolderType, kCreateFolder, &ref); + char home[MAX_PATH]; + FSRefMakePath(&ref, (UInt8 *)&home, MAX_PATH); + /* first +1 is "/", second is terminating null */ + const char *ext = ".conf"; + if (strlen(home) + 1 + strlen(appname) + strlen(ext) + 1 > maxlen) + { + out[0] = 0; + return; + } + + strcpy(out, home); + strcat(out, PATH_SEPARATOR_STRING); + strcat(out, appname); + strcat(out, ext); +} + +CFGPATH_FUNC void get_user_config_folder(char *out, unsigned int maxlen, const char *appname) +{ + FSRef ref; + FSFindFolder(kUserDomain, kApplicationSupportFolderType, kCreateFolder, &ref); + char home[MAX_PATH]; + FSRefMakePath(&ref, (UInt8 *)&home, MAX_PATH); + /* first +1 is "/", second is trailing "/", third is terminating null */ + if (strlen(home) + 1 + strlen(appname) + 1 + 1 > maxlen) + { + out[0] = 0; + return; + } + + strcpy(out, home); + strcat(out, PATH_SEPARATOR_STRING); + strcat(out, appname); + /* Make the .config/appname folder if it doesn't already exist */ + mkdir(out, 0755); + strcat(out, PATH_SEPARATOR_STRING); +} + +CFGPATH_FUNC void get_user_data_folder(char *out, unsigned int maxlen, const char *appname) +{ + /* No distinction under Windows or OS X */ + get_user_config_folder(out, maxlen, appname); +} + +CFGPATH_FUNC void get_user_cache_folder(char *out, unsigned int maxlen, const char *appname) +{ + /* No distinction under OS X */ + get_user_config_folder(out, maxlen, appname); +} + +#endif + +#endif \ No newline at end of file diff --git a/include/cfgpath/detail/cfgpath.impl.win32.h b/include/cfgpath/detail/cfgpath.impl.win32.h new file mode 100644 index 0000000..5575f61 --- /dev/null +++ b/include/cfgpath/detail/cfgpath.impl.win32.h @@ -0,0 +1,142 @@ +/** + * @file cfgpath.impl.win32.h + * @brief Cross platform methods for obtaining paths to configuration files. + * + * Copyright (C) 2013 Adam Nielsen + * Copyright (C) 2019 LonghronShen + * + * This code is placed in the public domain. You are free to use it for any + * purpose. If you add new platform support, please contribute a patch! + */ + +#if defined(_WIN32) || defined(WIN32) || defined(WIN64) + +#ifndef CFGPATH_WIN32_H +#define CFGPATH_WIN32_H + +#ifdef _MSC_VER +#if _MSC_VER > 1000 +#pragma once +#endif + +#if defined(CFGPATH_HEADER_ONLY) +#if defined(CFGPATH_IMPL) +#define CFGPATH_FUNC +#else +#define CFGPATH_FUNC static inline +#endif +#else +#define CFGPATH_FUNC +#endif + +#if !defined(CFGPATH_HEADER_ONLY) && !defined(WIN32_LEAN_AND_MEAN) +#define WIN32_LEAN_AND_MEAN +#endif +#if defined(NOMINMAX) || defined(CFGPATH_WIN_MINMAX) +#include +#include +#else +#define NOMINMAX +#include +#include +#undef NOMINMAX +#endif + +// #define inline __inline +#define mkdir _mkdir +#endif + +#ifndef MAX_PATH +#define MAX_PATH 260 +#endif +#ifndef PATH_SEPARATOR_CHAR +#define PATH_SEPARATOR_CHAR '\\' +#endif +#ifndef PATH_SEPARATOR_STRING +#define PATH_SEPARATOR_STRING "\\" +#endif + +CFGPATH_FUNC void get_user_config_file(char *out, size_t maxlen, const char *appname) +{ + if (maxlen < MAX_PATH) + { + out[0] = 0; + return; + } + if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, out))) + { + out[0] = 0; + return; + } + /* We don't try to create the AppData folder as it always exists already */ + size_t appname_len = strlen(appname); + if (strlen(out) + 1 + appname_len + strlen(".ini") + 1 > maxlen) + { + out[0] = 0; + return; + } + strcat(out, "\\"); + strcat(out, appname); + strcat(out, ".ini"); +} + +CFGPATH_FUNC void get_user_config_folder(char *out, size_t maxlen, const char *appname) +{ + if (maxlen < MAX_PATH) + { + out[0] = 0; + return; + } + if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, out))) + { + out[0] = 0; + return; + } + /* We don't try to create the AppData folder as it always exists already */ + size_t appname_len = strlen(appname); + if (strlen(out) + 1 + appname_len + 1 + 1 > maxlen) + { + out[0] = 0; + return; + } + strcat(out, "\\"); + strcat(out, appname); + /* Make the AppData\appname folder if it doesn't already exist */ + mkdir(out); + strcat(out, "\\"); +} + +CFGPATH_FUNC void get_user_data_folder(char *out, size_t maxlen, const char *appname) +{ + /* No distinction under Windows or OS X */ + get_user_config_folder(out, maxlen, appname); +} + +CFGPATH_FUNC void get_user_cache_folder(char *out, size_t maxlen, const char *appname) +{ + if (maxlen < MAX_PATH) + { + out[0] = 0; + return; + } + if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, out))) + { + out[0] = 0; + return; + } + /* We don't try to create the AppData folder as it always exists already */ + size_t appname_len = strlen(appname); + if (strlen(out) + 1 + appname_len + 1 + 1 > maxlen) + { + out[0] = 0; + return; + } + strcat(out, "\\"); + strcat(out, appname); + /* Make the AppData\appname folder if it doesn't already exist */ + mkdir(out); + strcat(out, "\\"); +} +#endif + +#endif \ No newline at end of file diff --git a/include/cfgpath/detail/polyfills/path_length.polyfills.h b/include/cfgpath/detail/polyfills/path_length.polyfills.h new file mode 100644 index 0000000..4e1f828 --- /dev/null +++ b/include/cfgpath/detail/polyfills/path_length.polyfills.h @@ -0,0 +1,61 @@ +/** + * @file path_length.polyfills.h + * @brief Cross platform methods for obtaining paths to configuration files. + * + * Copyright (C) 2013 Adam Nielsen + * Copyright (C) 2019 LonghronShen + * + * This code is placed in the public domain. You are free to use it for any + * purpose. If you add new platform support, please contribute a patch! + */ + +#ifndef PATH_LENGTH_POLYFILLS_H +#define PATH_LENGTH_POLYFILLS_H + +#ifdef _MSC_VER +#if _MSC_VER > 1000 +#pragma once +#endif +#endif + +#if defined(_WIN32) || defined(WIN32) || defined(WIN64) + +#ifndef MAX_PATH +#define MAX_PATH 260 +#endif + +#ifndef PATH_SEPARATOR_CHAR +#define PATH_SEPARATOR_CHAR '\\' +#endif + +#ifndef PATH_SEPARATOR_STRING +#define PATH_SEPARATOR_STRING "\\" +#endif + +#else + +#ifndef PATH_SEPARATOR_CHAR +#define PATH_SEPARATOR_CHAR '/' +#endif + +#ifndef PATH_SEPARATOR_STRING +#define PATH_SEPARATOR_STRING "/" +#endif + +#if defined(__linux__) + +#ifndef MAX_PATH +#define MAX_PATH 512 /* arbitrary value */ +#endif + +#elif defined(__APPLE__) + +#ifndef MAX_PATH +#include +#define MAX_PATH PATH_MAX +#endif + +#endif +#endif + +#endif \ No newline at end of file diff --git a/shlobj.h b/include/cfgpath/detail/polyfills/shlobj.polyfills.h similarity index 63% rename from shlobj.h rename to include/cfgpath/detail/polyfills/shlobj.polyfills.h index 2ae7e50..0d13476 100644 --- a/shlobj.h +++ b/include/cfgpath/detail/polyfills/shlobj.polyfills.h @@ -1,18 +1,22 @@ /** - * @file shlobj.h - * @brief Dummy file to allow running the Win32 tests on non-Windows platforms. + * @file shlobj.polyfills.h + * @brief Cross platform methods for obtaining paths to configuration files. * * Copyright (C) 2013 Adam Nielsen + * Copyright (C) 2019 LonghronShen * * This code is placed in the public domain. You are free to use it for any * purpose. If you add new platform support, please contribute a patch! */ +#if SHLOBJ_POLYFILLS_H +#define SHLOBJ_POLYFILLS_H + #ifdef CSIDL_APPDATA #error This file should not be used under Windows, use the system version instead! #endif -#define CSIDL_APPDATA 26 /* roaming appdata */ +#define CSIDL_APPDATA 26 /* roaming appdata */ #define CSIDL_LOCAL_APPDATA 28 #define S_OK 0 @@ -22,3 +26,5 @@ #define SUCCEEDED(hr) ((hr) >= 0) #define MAX_PATH 256 + +#endif \ No newline at end of file diff --git a/src/cfgpath.core.c b/src/cfgpath.core.c new file mode 100644 index 0000000..a41b627 --- /dev/null +++ b/src/cfgpath.core.c @@ -0,0 +1,20 @@ +/** + * @file cfgpath.core.c + * @brief Cross platform methods for obtaining paths to configuration files. + * + * Copyright (C) 2013 Adam Nielsen + * Copyright (C) 2019 LonghronShen + * + * This code is placed in the public domain. You are free to use it for any + * purpose. If you add new platform support, please contribute a patch! + */ + +#ifndef CFGPATH_HEADER_ONLY +#define CFGPATH_HEADER_ONLY +#endif + +#ifndef CFGPATH_IMPL +#define CFGPATH_IMPL +#endif + +#include diff --git a/src/cfgpath.cpp b/src/cfgpath.cpp new file mode 100644 index 0000000..62597c9 --- /dev/null +++ b/src/cfgpath.cpp @@ -0,0 +1,61 @@ +/** + * @file cfgpath.cpp + * @brief Cross platform methods for obtaining paths to configuration files. + * + * Copyright (C) 2013 Adam Nielsen + * Copyright (C) 2019 LonghronShen + * + * This code is placed in the public domain. You are free to use it for any + * purpose. If you add new platform support, please contribute a patch! + */ + +#if defined(CFGPATH_HEADER_ONLY) +#undef CFGPATH_HEADER_ONLY +#endif + +#include + +#include +#include + +using namespace std; + +template +std::string inline call_function(const string &appname, + cfgpath_callback callback) { + char path[MAX_PATH]; + callback(path, sizeof(path), appname.c_str()); + if (path[0] == 0) { + return ""; + } + return path; +} + +std::string +libcfgpath::cfgpath::get_folder_path(libcfgpath::known_spefial_folder folder, + const std::string &appname) { + switch (folder) { + case libcfgpath::known_spefial_folder::CONFIG: + return std::move(call_function(appname, get_user_config_folder)); + case libcfgpath::known_spefial_folder::DATA: + return std::move(call_function(appname, get_user_data_folder)); + case libcfgpath::known_spefial_folder::CACHE: + return std::move(call_function(appname, get_user_cache_folder)); + default: + throw std::runtime_error("Unkown special folder type given."); + } +} + +std::string +libcfgpath::cfgpath::get_file_path(libcfgpath::known_spefial_folder folder, + const std::string &appname, + const std::string &fileName) { + switch (folder) { + case libcfgpath::known_spefial_folder::CONFIG: + return std::move(call_function(appname, get_user_config_folder)); + case libcfgpath::known_spefial_folder::DATA: + case libcfgpath::known_spefial_folder::CACHE: + default: + return libcfgpath::cfgpath::get_folder_path(folder, appname) + fileName; + } +} diff --git a/test-linux.c b/test-linux.c deleted file mode 100644 index 8bda730..0000000 --- a/test-linux.c +++ /dev/null @@ -1,174 +0,0 @@ -/** - * @file test-linux.c - * @brief cfgpath.h test code for the Linux platform. - * - * Copyright (C) 2013 Adam Nielsen - * - * This code is placed in the public domain. You are free to use it for any - * purpose. If you add new platform support, please contribute a patch! - */ - -#include -#include - -#ifndef __linux__ -#define __linux__ -#endif -#undef WIN32 -#define getenv test_getenv -#define mkdir test_mkdir -#include "cfgpath.h" - -int test_env_xdg_valid; /* Does $XDG_CONFIG_HOME exist? */ -int test_env_home_valid; /* Does $HOME exist? */ - -char getenv_buffer[256]; - -char *test_getenv(const char *var) -{ - if (test_env_xdg_valid && (strcmp(var, "XDG_CONFIG_HOME") == 0)) { - strcpy(getenv_buffer, "/home/test/.config"); - return getenv_buffer; - } - if (test_env_home_valid && (strcmp(var, "HOME") == 0)) { - strcpy(getenv_buffer, "/home/test"); - return getenv_buffer; - } - return NULL; -} - -int test_mkdir(const char *path, mode_t mode) -{ - return 0; -} - -#define TOSTRING_X(x) #x -#define TOSTRING(x) TOSTRING_X(x) -#define RUN_TEST(result, msg) \ - TEST_FUNC(buffer, sizeof(buffer), "test-linux"); \ - CHECK_RESULT(result, msg) - -#define CHECK_RESULT(result, msg) \ - if (strcmp(buffer, result) != 0) { \ - printf("FAIL: %s:%d " TOSTRING(TEST_FUNC) "() returned the wrong value.\n" \ - "Expected: %s\nGot: %s\n", __FILE__, __LINE__, result, buffer); \ - return 1; \ - } else { \ - printf("PASS: " TOSTRING(TEST_FUNC) "() " msg "\n"); \ - } - -int main(int argc, char *argv[]) -{ - char buffer[256]; - -/* - * get_user_config_file() - */ - -#define TEST_RESULT "/home/test/.config/test-linux.conf" -#define TEST_FUNC get_user_config_file - - test_env_xdg_valid = 1; - test_env_home_valid = 1; - TEST_FUNC(buffer, 5, "test-linux"); - CHECK_RESULT("", "returns empty string when buffer is too small."); - - test_env_xdg_valid = 1; - test_env_home_valid = 1; - RUN_TEST(TEST_RESULT, "works with $XDG_CONFIG_HOME."); - - test_env_xdg_valid = 0; - test_env_home_valid = 1; - RUN_TEST(TEST_RESULT, "works with $HOME and not $XDG_CONFIG_HOME."); - - test_env_xdg_valid = 0; - test_env_home_valid = 0; - RUN_TEST("", "returns empty string when $XDG_CONFIG_HOME and $HOME are absent."); - -#undef TEST_FUNC -#undef TEST_RESULT - -/* - * get_user_config_folder() - */ - -#define TEST_RESULT "/home/test/.config/test-linux/" -#define TEST_FUNC get_user_config_folder - - test_env_xdg_valid = 1; - test_env_home_valid = 1; - TEST_FUNC(buffer, 5, "test-linux"); - CHECK_RESULT("", "returns empty string when buffer is too small."); - - test_env_xdg_valid = 1; - test_env_home_valid = 1; - RUN_TEST(TEST_RESULT, "works with $XDG_CONFIG_HOME."); - - test_env_xdg_valid = 0; - test_env_home_valid = 1; - RUN_TEST(TEST_RESULT, "works with $HOME and not $XDG_CONFIG_HOME."); - - test_env_xdg_valid = 0; - test_env_home_valid = 0; - RUN_TEST("", "returns empty string when $XDG_CONFIG_HOME and $HOME are absent."); - -#undef TEST_FUNC -#undef TEST_RESULT - -/* - * get_user_data_folder() - */ - -#define TEST_RESULT "/home/test/.local/share/test-linux/" -#define TEST_FUNC get_user_data_folder - - test_env_xdg_valid = 1; - test_env_home_valid = 1; - TEST_FUNC(buffer, 5, "test-linux"); - CHECK_RESULT("", "returns empty string when buffer is too small."); - - test_env_xdg_valid = 1; - test_env_home_valid = 1; - RUN_TEST(TEST_RESULT, "works with $XDG_CONFIG_HOME."); - - test_env_xdg_valid = 0; - test_env_home_valid = 1; - RUN_TEST(TEST_RESULT, "works with $HOME and not $XDG_CONFIG_HOME."); - - test_env_xdg_valid = 0; - test_env_home_valid = 0; - RUN_TEST("", "returns empty string when $XDG_CONFIG_HOME and $HOME are absent."); - -#undef TEST_FUNC -#undef TEST_RESULT - -/* - * get_user_cache_folder() - */ - -#define TEST_RESULT "/home/test/.cache/test-linux/" -#define TEST_FUNC get_user_cache_folder - - test_env_xdg_valid = 1; - test_env_home_valid = 1; - TEST_FUNC(buffer, 5, "test-linux"); - CHECK_RESULT("", "returns empty string when buffer is too small."); - - test_env_xdg_valid = 1; - test_env_home_valid = 1; - RUN_TEST(TEST_RESULT, "works with $XDG_CONFIG_HOME."); - - test_env_xdg_valid = 0; - test_env_home_valid = 1; - RUN_TEST(TEST_RESULT, "works with $HOME and not $XDG_CONFIG_HOME."); - - test_env_xdg_valid = 0; - test_env_home_valid = 0; - RUN_TEST("", "returns empty string when $XDG_CONFIG_HOME and $HOME are absent."); - -#undef TEST_FUNC -#undef TEST_RESULT - - printf("All tests passed for platform: Linux.\n"); - return 0; -} diff --git a/test-win.c b/test-win.c deleted file mode 100644 index 15a6d77..0000000 --- a/test-win.c +++ /dev/null @@ -1,173 +0,0 @@ -/** - * @file test-linux.c - * @brief cfgpath.h test code for the Linux platform. - * - * Copyright (C) 2013 Adam Nielsen - * - * This code is placed in the public domain. You are free to use it for any - * purpose. If you add new platform support, please contribute a patch! - */ - -#include -#include - -#define SHGetFolderPath test_SHGetFolderPath -#define mkdir test_mkdir -#undef __linux__ -#ifndef WIN32 -#define WIN32 -#endif -#include "cfgpath.h" - -const char *set_appdata; -const char *set_appdata_local; -int set_retval; - -/* Fake implementation of SHGetFolderPath() that fails when and how we want */ -int test_SHGetFolderPath(void *hwndOwner, int nFolder, void *hToken, - int dwFlags, char *pszPath) -{ - /* Hopefully trigger an error if the buffer is too small */ - pszPath[MAX_PATH - 1] = 0; - - switch (nFolder) { - case CSIDL_APPDATA: - if (set_appdata) { - strcpy(pszPath, set_appdata); - return set_retval; - } - break; - case CSIDL_LOCAL_APPDATA: - if (set_appdata_local) { - strcpy(pszPath, set_appdata_local); - return set_retval; - } - break; - } - return E_FAIL; -} - -int test_mkdir(const char *path) -{ - return 0; -} - -#define TOSTRING_X(x) #x -#define TOSTRING(x) TOSTRING_X(x) -#define RUN_TEST(result, msg) \ - TEST_FUNC(buffer, sizeof(buffer), "test-win"); \ - CHECK_RESULT(result, msg) - -#define CHECK_RESULT(result, msg) \ - if (strcmp(buffer, result) != 0) { \ - printf("FAIL: %s:%d " TOSTRING(TEST_FUNC) "() " msg " Test failed, " \ - "returning the wrong value.\n" \ - "Expected: %s\nGot: %s\n", __FILE__, __LINE__, result, buffer); \ - return 1; \ - } else { \ - printf("PASS: " TOSTRING(TEST_FUNC) "() " msg "\n"); \ - } - -int main(int argc, char *argv[]) -{ - char buffer[256]; - -/* - * get_user_config_file() - */ - -#define TEST_RESULT "C:\\Users\\test-win\\AppData\\Roaming\\test-win.ini" -#define TEST_FUNC get_user_config_file - - set_retval = S_OK; - set_appdata = "C:\\Users\\test-win\\AppData\\Roaming"; - - TEST_FUNC(buffer, 5, "test-win"); - CHECK_RESULT("", "returns empty string when buffer is too small."); - - RUN_TEST(TEST_RESULT, "works with CSIDL_APPDATA."); - - set_retval = S_FALSE; - RUN_TEST(TEST_RESULT, "works if folder doesn't exist (S_FALSE)."); - - set_appdata = NULL; - RUN_TEST("", "fails with missing CSIDL_APPDATA."); - -#undef TEST_FUNC -#undef TEST_RESULT - -/* - * get_user_config_folder() - */ - -#define TEST_RESULT "C:\\Users\\test-win\\AppData\\Roaming\\test-win\\" -#define TEST_FUNC get_user_config_folder - - set_retval = S_OK; - set_appdata = "C:\\Users\\test-win\\AppData\\Roaming"; - - TEST_FUNC(buffer, 5, "test-win"); - CHECK_RESULT("", "returns empty string when buffer is too small."); - - RUN_TEST(TEST_RESULT, "works with CSIDL_APPDATA."); - - set_retval = S_FALSE; - RUN_TEST(TEST_RESULT, "works if folder doesn't exist (S_FALSE)."); - - set_appdata = NULL; - RUN_TEST("", "fails with missing CSIDL_APPDATA."); - -#undef TEST_FUNC -#undef TEST_RESULT - -/* - * get_user_data_folder() - */ - -#define TEST_RESULT "C:\\Users\\test-win\\AppData\\Roaming\\test-win\\" -#define TEST_FUNC get_user_data_folder - - set_retval = S_OK; - set_appdata = "C:\\Users\\test-win\\AppData\\Roaming"; - - TEST_FUNC(buffer, 5, "test-win"); - CHECK_RESULT("", "returns empty string when buffer is too small."); - - RUN_TEST(TEST_RESULT, "works with CSIDL_APPDATA."); - - set_retval = S_FALSE; - RUN_TEST(TEST_RESULT, "works if folder doesn't exist (S_FALSE)."); - - set_appdata = NULL; - RUN_TEST("", "fails with missing CSIDL_APPDATA."); - -#undef TEST_FUNC -#undef TEST_RESULT - -/* - * get_user_cache_folder() - */ - -#define TEST_RESULT "C:\\Users\\test-win\\AppData\\Local\\test-win\\" -#define TEST_FUNC get_user_cache_folder - - set_retval = S_OK; - set_appdata_local = "C:\\Users\\test-win\\AppData\\Local"; - - TEST_FUNC(buffer, 5, "test-win"); - CHECK_RESULT("", "returns empty string when buffer is too small."); - - RUN_TEST(TEST_RESULT, "works with CSIDL_LOCAL_APPDATA."); - - set_retval = S_FALSE; - RUN_TEST(TEST_RESULT, "works if folder doesn't exist (S_FALSE)."); - - set_appdata_local = NULL; - RUN_TEST("", "fails with missing CSIDL_LOCAL_APPDATA."); - -#undef TEST_FUNC -#undef TEST_RESULT - - printf("All tests passed for platform: Windows.\n"); - return 0; -} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..7cfd944 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.13) + +project(cfgpath_tests VERSION 1.0.0 LANGUAGES C CXX) + +file(GLOB CFGPATH_TESTS_SRC + ${CMAKE_CURRENT_LIST_DIR}/*.c) + +add_executable(cfgpath_test_runner ${CFGPATH_TESTS_SRC}) +target_link_libraries(cfgpath_test_runner cfgpath) + +add_test(cfgpath_test cfgpath_test_runner) diff --git a/tests/test-linux.c b/tests/test-linux.c new file mode 100644 index 0000000..dc97fd7 --- /dev/null +++ b/tests/test-linux.c @@ -0,0 +1,191 @@ +/** + * @file test-linux.c + * @brief cfgpath.h test code for the Linux platform. + * + * Copyright (C) 2013 Adam Nielsen + * Copyright (C) 2019 LonghronShen + * + * This code is placed in the public domain. You are free to use it for any + * purpose. If you add new platform support, please contribute a patch! + */ + +#ifdef __linux__ + +#undef WIN32 +#undef _WIN32 +#undef __APPLE__ + +#include +#include + +#define CFGPATH_HEADER_ONLY + +#define getenv test_getenv +#define mkdir test_mkdir + +#include + +#define XDG_CONFIG_HOME_VALUE "/home/test/.config" +#define HOME_VALUE "/home/test" + +int test_env_xdg_valid; /* Does $XDG_CONFIG_HOME exist? */ +int test_env_home_valid; /* Does $HOME exist? */ + +char getenv_buffer[256]; + +char *test_getenv(const char *var) +{ + if (test_env_xdg_valid && (strcmp(var, "XDG_CONFIG_HOME") == 0)) + { + strcpy(getenv_buffer, XDG_CONFIG_HOME_VALUE); + return getenv_buffer; + } + if (test_env_home_valid && (strcmp(var, "HOME") == 0)) + { + strcpy(getenv_buffer, HOME_VALUE); + return getenv_buffer; + } + return NULL; +} + +int test_mkdir(const char *path, mode_t mode) +{ + return 0; +} + +#define TOSTRING_X(x) #x +#define TOSTRING(x) TOSTRING_X(x) +#define RUN_TEST(result, msg) \ + TEST_FUNC(buffer, sizeof(buffer), "test-linux"); \ + CHECK_RESULT(result, msg) + +#define CHECK_RESULT(result, msg) \ + if (strcmp(buffer, result) != 0) \ + { \ + printf("FAIL: %s:%d " TOSTRING(TEST_FUNC) "() returned the wrong value.\n" \ + "Expected: %s\nGot: %s\n", \ + __FILE__, __LINE__, result, buffer); \ + return 1; \ + } \ + else \ + { \ + printf("PASS: " TOSTRING(TEST_FUNC) "() " msg "\n"); \ + } + +int main(int argc, char *argv[]) +{ + char buffer[256]; + + /* + * get_user_config_file() + */ + +#define TEST_RESULT "/home/test/.config/test-linux.conf" +#define TEST_FUNC get_user_config_file + + test_env_xdg_valid = 1; + test_env_home_valid = 1; + TEST_FUNC(buffer, 5, "test-linux"); + CHECK_RESULT("", "returns empty string when buffer is too small."); + + test_env_xdg_valid = 1; + test_env_home_valid = 1; + RUN_TEST(TEST_RESULT, "works with $XDG_CONFIG_HOME."); + + test_env_xdg_valid = 0; + test_env_home_valid = 1; + RUN_TEST(TEST_RESULT, "works with $HOME and not $XDG_CONFIG_HOME."); + + test_env_xdg_valid = 0; + test_env_home_valid = 0; + RUN_TEST("", "returns empty string when $XDG_CONFIG_HOME and $HOME are absent."); + +#undef TEST_FUNC +#undef TEST_RESULT + + /* + * get_user_config_folder() + */ + +#define TEST_RESULT "/home/test/.config/test-linux/" +#define TEST_FUNC get_user_config_folder + + test_env_xdg_valid = 1; + test_env_home_valid = 1; + TEST_FUNC(buffer, 5, "test-linux"); + CHECK_RESULT("", "returns empty string when buffer is too small."); + + test_env_xdg_valid = 1; + test_env_home_valid = 1; + RUN_TEST(TEST_RESULT, "works with $XDG_CONFIG_HOME."); + + test_env_xdg_valid = 0; + test_env_home_valid = 1; + RUN_TEST(TEST_RESULT, "works with $HOME and not $XDG_CONFIG_HOME."); + + test_env_xdg_valid = 0; + test_env_home_valid = 0; + RUN_TEST("", "returns empty string when $XDG_CONFIG_HOME and $HOME are absent."); + +#undef TEST_FUNC +#undef TEST_RESULT + + /* + * get_user_data_folder() + */ + +#define TEST_RESULT "/home/test/.local/share/test-linux/" +#define TEST_FUNC get_user_data_folder + + test_env_xdg_valid = 1; + test_env_home_valid = 1; + TEST_FUNC(buffer, 5, "test-linux"); + CHECK_RESULT("", "returns empty string when buffer is too small."); + + test_env_xdg_valid = 1; + test_env_home_valid = 1; + RUN_TEST(TEST_RESULT, "works with $XDG_CONFIG_HOME."); + + test_env_xdg_valid = 0; + test_env_home_valid = 1; + RUN_TEST(TEST_RESULT, "works with $HOME and not $XDG_CONFIG_HOME."); + + test_env_xdg_valid = 0; + test_env_home_valid = 0; + RUN_TEST("", "returns empty string when $XDG_CONFIG_HOME and $HOME are absent."); + +#undef TEST_FUNC +#undef TEST_RESULT + + /* + * get_user_cache_folder() + */ + +#define TEST_RESULT "/home/test/.cache/test-linux/" +#define TEST_FUNC get_user_cache_folder + + test_env_xdg_valid = 1; + test_env_home_valid = 1; + TEST_FUNC(buffer, 5, "test-linux"); + CHECK_RESULT("", "returns empty string when buffer is too small."); + + test_env_xdg_valid = 1; + test_env_home_valid = 1; + RUN_TEST(TEST_RESULT, "works with $XDG_CONFIG_HOME."); + + test_env_xdg_valid = 0; + test_env_home_valid = 1; + RUN_TEST(TEST_RESULT, "works with $HOME and not $XDG_CONFIG_HOME."); + + test_env_xdg_valid = 0; + test_env_home_valid = 0; + RUN_TEST("", "returns empty string when $XDG_CONFIG_HOME and $HOME are absent."); + +#undef TEST_FUNC +#undef TEST_RESULT + + printf("All tests passed for platform: Linux.\n"); + return 0; +} + +#endif \ No newline at end of file diff --git a/tests/test-osx.c b/tests/test-osx.c new file mode 100644 index 0000000..e093d60 --- /dev/null +++ b/tests/test-osx.c @@ -0,0 +1,189 @@ +/** + * @file test-osx.c + * @brief cfgpath.h test code for the OS X platform. + * + * Copyright (C) 2013 Adam Nielsen + * Copyright (C) 2019 LonghronShen + * + * This code is placed in the public domain. You are free to use it for any + * purpose. If you add new platform support, please contribute a patch! + */ + +#if __APPLE__ + +#undef WIN32 +#undef _WIN32 +#undef __linux__ + +#include +#include + +#define CFGPATH_HEADER_ONLY + +#ifdef FSRefMakePath +#undef FSRefMakePath +#define FSRefMakePath test_FSRefMakePath +#endif + +#ifdef mkdir +#undef mkdir +#define mkdir test_mkdir +#endif + +#include + +#define HOME_VALUE "/Users/test/Library/Application Support/" +#define XDG_CONFIG_HOME_VALUE HOME_VALUE ".config" + +int test_env_xdg_valid; /* Does $XDG_CONFIG_HOME exist? */ +int test_env_home_valid; /* Does $HOME exist? */ + +OSStatus test_FSRefMakePath(const FSRef *ref, UInt8 *path, UInt32 pathBufferSize) +{ + printf("calling mock function test_FSRefMakePath.\r\n"); + memset((void *)path, 0, pathBufferSize); + strcpy((char *)path, HOME_VALUE); + return (OSStatus)0; +} + +int test_mkdir(const char *path, mode_t mode) +{ + return 0; +} + +#define TOSTRING_X(x) #x +#define TOSTRING(x) TOSTRING_X(x) +#define RUN_TEST(result, msg) \ + TEST_FUNC(buffer, sizeof(buffer), "test-osx"); \ + CHECK_RESULT(result, msg) + +#define CHECK_RESULT(result, msg) \ + if (strcmp(buffer, result) != 0) \ + { \ + printf("FAIL: %s:%d " TOSTRING(TEST_FUNC) "() returned the wrong value.\n" \ + "Expected: %s\nGot: %s\n", \ + __FILE__, __LINE__, result, buffer); \ + return 1; \ + } \ + else \ + { \ + printf("PASS: " TOSTRING(TEST_FUNC) "() " msg "\n"); \ + } + +int main(int argc, char *argv[]) +{ + char buffer[MAX_PATH]; + + /* + * get_user_config_file() + */ + +#define TEST_RESULT HOME_VALUE "test-osx.conf" +#define TEST_FUNC get_user_config_file + + test_env_xdg_valid = 1; + test_env_home_valid = 1; + TEST_FUNC(buffer, 5, "test-osx"); + CHECK_RESULT("", "returns empty string when buffer is too small."); + + test_env_xdg_valid = 1; + test_env_home_valid = 1; + RUN_TEST(TEST_RESULT, "works with $XDG_CONFIG_HOME."); + + test_env_xdg_valid = 0; + test_env_home_valid = 1; + RUN_TEST(TEST_RESULT, "works with $HOME and not $XDG_CONFIG_HOME."); + + test_env_xdg_valid = 0; + test_env_home_valid = 0; + RUN_TEST("", "returns empty string when $XDG_CONFIG_HOME and $HOME are absent."); + +#undef TEST_FUNC +#undef TEST_RESULT + + /* + * get_user_config_folder() + */ + +#define TEST_RESULT "/home/test/.config/test-osx/" +#define TEST_FUNC get_user_config_folder + + test_env_xdg_valid = 1; + test_env_home_valid = 1; + TEST_FUNC(buffer, 5, "test-osx"); + CHECK_RESULT("", "returns empty string when buffer is too small."); + + test_env_xdg_valid = 1; + test_env_home_valid = 1; + RUN_TEST(TEST_RESULT, "works with $XDG_CONFIG_HOME."); + + test_env_xdg_valid = 0; + test_env_home_valid = 1; + RUN_TEST(TEST_RESULT, "works with $HOME and not $XDG_CONFIG_HOME."); + + test_env_xdg_valid = 0; + test_env_home_valid = 0; + RUN_TEST("", "returns empty string when $XDG_CONFIG_HOME and $HOME are absent."); + +#undef TEST_FUNC +#undef TEST_RESULT + + /* + * get_user_data_folder() + */ + +#define TEST_RESULT "/home/test/.local/share/test-osx/" +#define TEST_FUNC get_user_data_folder + + test_env_xdg_valid = 1; + test_env_home_valid = 1; + TEST_FUNC(buffer, 5, "test-osx"); + CHECK_RESULT("", "returns empty string when buffer is too small."); + + test_env_xdg_valid = 1; + test_env_home_valid = 1; + RUN_TEST(TEST_RESULT, "works with $XDG_CONFIG_HOME."); + + test_env_xdg_valid = 0; + test_env_home_valid = 1; + RUN_TEST(TEST_RESULT, "works with $HOME and not $XDG_CONFIG_HOME."); + + test_env_xdg_valid = 0; + test_env_home_valid = 0; + RUN_TEST("", "returns empty string when $XDG_CONFIG_HOME and $HOME are absent."); + +#undef TEST_FUNC +#undef TEST_RESULT + + /* + * get_user_cache_folder() + */ + +#define TEST_RESULT "/home/test/.cache/test-osx/" +#define TEST_FUNC get_user_cache_folder + + test_env_xdg_valid = 1; + test_env_home_valid = 1; + TEST_FUNC(buffer, 5, "test-osx"); + CHECK_RESULT("", "returns empty string when buffer is too small."); + + test_env_xdg_valid = 1; + test_env_home_valid = 1; + RUN_TEST(TEST_RESULT, "works with $XDG_CONFIG_HOME."); + + test_env_xdg_valid = 0; + test_env_home_valid = 1; + RUN_TEST(TEST_RESULT, "works with $HOME and not $XDG_CONFIG_HOME."); + + test_env_xdg_valid = 0; + test_env_home_valid = 0; + RUN_TEST("", "returns empty string when $XDG_CONFIG_HOME and $HOME are absent."); + +#undef TEST_FUNC +#undef TEST_RESULT + + printf("All tests passed for platform: OS X.\n"); + return 0; +} + +#endif \ No newline at end of file diff --git a/tests/test-win.c b/tests/test-win.c new file mode 100644 index 0000000..e0b3bbb --- /dev/null +++ b/tests/test-win.c @@ -0,0 +1,190 @@ +/** + * @file test-win.c + * @brief cfgpath.h test code for the Windows platform. + * + * Copyright (C) 2013 Adam Nielsen + * Copyright (C) 2019 LonghronShen + * + * This code is placed in the public domain. You are free to use it for any + * purpose. If you add new platform support, please contribute a patch! + */ + +#if _WIN32 || defined(WIN32) || defined(WIN64) + +#undef __linux__ +#undef __APPLE__ + +#ifndef WIN32 +#define WIN32 +#endif + +#include +#include + +#define CFGPATH_HEADER_ONLY + +#define SHGetFolderPath test_SHGetFolderPath +#define mkdir test_mkdir + +#include + +const char *set_appdata; +const char *set_appdata_local; +int set_retval; + +/* Fake implementation of SHGetFolderPath() that fails when and how we want */ +int test_SHGetFolderPath(void *hwndOwner, int nFolder, void *hToken, int dwFlags, char *pszPath) +{ + /* Hopefully trigger an error if the buffer is too small */ + pszPath[MAX_PATH - 1] = 0; + + switch (nFolder) + { + case CSIDL_APPDATA: + if (set_appdata) + { + strcpy(pszPath, set_appdata); + return set_retval; + } + break; + case CSIDL_LOCAL_APPDATA: + if (set_appdata_local) + { + strcpy(pszPath, set_appdata_local); + return set_retval; + } + break; + } + return E_FAIL; +} + +int test_mkdir(const char *path) +{ + return 0; +} + +#define TOSTRING_X(x) #x +#define TOSTRING(x) TOSTRING_X(x) +#define RUN_TEST(result, msg) \ + TEST_FUNC(buffer, sizeof(buffer), "test-win"); \ + CHECK_RESULT(result, msg) + +#define CHECK_RESULT(result, msg) \ + if (strcmp(buffer, result) != 0) \ + { \ + printf("FAIL: %s:%d " TOSTRING(TEST_FUNC) "() " msg " Test failed, " \ + "returning the wrong value.\n" \ + "Expected: %s\nGot: %s\n", \ + __FILE__, __LINE__, result, buffer); \ + return 1; \ + } \ + else \ + { \ + printf("PASS: " TOSTRING(TEST_FUNC) "() " msg "\n"); \ + } + +int main(int argc, char *argv[]) +{ + char buffer[256]; + + /* + * get_user_config_file() + */ + +#define TEST_RESULT "C:\\Users\\test-win\\AppData\\Roaming\\test-win.ini" +#define TEST_FUNC get_user_config_file + + set_retval = S_OK; + set_appdata = "C:\\Users\\test-win\\AppData\\Roaming"; + + TEST_FUNC(buffer, 5, "test-win"); + CHECK_RESULT("", "returns empty string when buffer is too small."); + + RUN_TEST(TEST_RESULT, "works with CSIDL_APPDATA."); + + set_retval = S_FALSE; + RUN_TEST(TEST_RESULT, "works if folder doesn't exist (S_FALSE)."); + + set_appdata = NULL; + RUN_TEST("", "fails with missing CSIDL_APPDATA."); + +#undef TEST_FUNC +#undef TEST_RESULT + + /* + * get_user_config_folder() + */ + +#define TEST_RESULT "C:\\Users\\test-win\\AppData\\Roaming\\test-win\\" +#define TEST_FUNC get_user_config_folder + + set_retval = S_OK; + set_appdata = "C:\\Users\\test-win\\AppData\\Roaming"; + + TEST_FUNC(buffer, 5, "test-win"); + CHECK_RESULT("", "returns empty string when buffer is too small."); + + RUN_TEST(TEST_RESULT, "works with CSIDL_APPDATA."); + + set_retval = S_FALSE; + RUN_TEST(TEST_RESULT, "works if folder doesn't exist (S_FALSE)."); + + set_appdata = NULL; + RUN_TEST("", "fails with missing CSIDL_APPDATA."); + +#undef TEST_FUNC +#undef TEST_RESULT + + /* + * get_user_data_folder() + */ + +#define TEST_RESULT "C:\\Users\\test-win\\AppData\\Roaming\\test-win\\" +#define TEST_FUNC get_user_data_folder + + set_retval = S_OK; + set_appdata = "C:\\Users\\test-win\\AppData\\Roaming"; + + TEST_FUNC(buffer, 5, "test-win"); + CHECK_RESULT("", "returns empty string when buffer is too small."); + + RUN_TEST(TEST_RESULT, "works with CSIDL_APPDATA."); + + set_retval = S_FALSE; + RUN_TEST(TEST_RESULT, "works if folder doesn't exist (S_FALSE)."); + + set_appdata = NULL; + RUN_TEST("", "fails with missing CSIDL_APPDATA."); + +#undef TEST_FUNC +#undef TEST_RESULT + + /* + * get_user_cache_folder() + */ + +#define TEST_RESULT "C:\\Users\\test-win\\AppData\\Local\\test-win\\" +#define TEST_FUNC get_user_cache_folder + + set_retval = S_OK; + set_appdata_local = "C:\\Users\\test-win\\AppData\\Local"; + + TEST_FUNC(buffer, 5, "test-win"); + CHECK_RESULT("", "returns empty string when buffer is too small."); + + RUN_TEST(TEST_RESULT, "works with CSIDL_LOCAL_APPDATA."); + + set_retval = S_FALSE; + RUN_TEST(TEST_RESULT, "works if folder doesn't exist (S_FALSE)."); + + set_appdata_local = NULL; + RUN_TEST("", "fails with missing CSIDL_LOCAL_APPDATA."); + +#undef TEST_FUNC +#undef TEST_RESULT + + printf("All tests passed for platform: Windows.\n"); + return 0; +} + +#endif \ No newline at end of file