├── .githooks └── pre-commit ├── .github └── workflows │ └── doxygen.yml ├── .gitignore ├── CHANGELOG.md ├── CMakeLists.txt ├── LICENSE ├── README.md ├── docs ├── CMakeLists.txt ├── custom.css └── layout.xml ├── examples ├── CMakeLists.txt ├── compilation.dox ├── custom.c ├── lists.c ├── print.c └── ver.c ├── libXDGdirs.pc.in ├── src ├── CMakeLists.txt ├── xdgdirs.c └── xdgdirs.h └── tests ├── CMakeLists.txt ├── test.c └── test1 /.githooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | if ! git diff --quiet --cached src/*; then 4 | if ! git diff --cached -U0 CMakeLists.txt | grep -q ' VERSION'; then 5 | ver="$(git describe --always --long | sed 's/^v//')" 6 | ver="${ver%-*}-$(git branch --show-current)" 7 | echo "Updating version macro to $ver" 8 | sed -i 's/#define XDGDIRS_VER ".*"/#define XDGDIRS_VER "'"$ver"'"/' src/xdgdirs.h 9 | git add src/xdgdirs.h 10 | fi 11 | fi 12 | -------------------------------------------------------------------------------- /.github/workflows/doxygen.yml: -------------------------------------------------------------------------------- 1 | name: Doxygen 2 | on: 3 | push: 4 | branches: [ master ] 5 | pull_request: 6 | branches: [ master ] 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | with: 13 | fetch-depth: 0 14 | submodules: recursive 15 | - name: Install Doxygen 16 | run: | 17 | ver="1.9.8" 18 | dir="$PWD" 19 | cd "${TMPDIR:-/tmp}" 20 | curl -OL https://www.doxygen.nl/files/doxygen-"$ver".linux.bin.tar.gz 21 | tar xvzf doxygen-"$ver".linux.bin.tar.gz 22 | cd doxygen-"$ver" 23 | sudo make install 24 | cd "$dir" 25 | - name: CMake 26 | run: cmake -B build -H. -DCMAKE_BUILD_TYPE=Release 27 | - name: Make docs 28 | run: cmake --build build --target docs 29 | - name: Deploy 30 | uses: peaceiris/actions-gh-pages@v3 31 | with: 32 | github_token: ${{ secrets.GITHUB_TOKEN }} 33 | publish_dir: ./build/docs/html 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | All notable changes to this project will be documented in this file. 5 | 6 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), 7 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 8 | 9 | ## [Unreleased] 10 | 11 | ## [1.1.3] - 2024-11-08 12 | ### Changed 13 | 14 | - Use Git hooks for updating version in header 15 | - Update badges in README 16 | - Update URLs to repo in README 17 | - Update URLs in CHANGELOG 18 | 19 | ### Removed 20 | 21 | - AUTHORS file 22 | - CONTRIBUTING.md file 23 | 24 | ## [1.1.2] - 2024-07-06 25 | ### Added 26 | 27 | - pkg-config file 28 | 29 | ### Changed 30 | 31 | - Use Git tags for generating version header 32 | 33 | ## [1.1.1] - 2023-11-11 34 | ### Fixed 35 | 36 | - Missing `void` params 37 | 38 | ## [1.1.0] - 2022-04-27 39 | ### Added 40 | 41 | - `XDG_STATE_HOME` 42 | 43 | ### Changed 44 | 45 | - Updated to XDG Base Directory Specification version 0.8 46 | 47 | ## [1.0.5] - 2022-02-18 48 | ### Fixed 49 | 50 | - Define undefined `XDGDIRS_VER` 51 | - Return value of `xdgDirs_init()` 52 | 53 | ## [1.0.4] - 2020-12-22 54 | ### Fixed 55 | 56 | - `xdgDirsList` in C++ 57 | 58 | ## [1.0.3] - 2020-12-22 59 | ### Changed 60 | 61 | - Use `vsnprintf()` in `xdgDirs_getenv()` 62 | 63 | ### Fixed 64 | 65 | - Return from `xdgDirs_getenv()` after assigning `NULL` 66 | - Missing `extern C` 67 | 68 | ## [1.0.2] - 2020-12-21 69 | ### Added 70 | 71 | - LGTM and CodeFactor project badges in README 72 | 73 | ### Changed 74 | 75 | - Rename `LIB_NAME` to `XDGDIRS` in CMakeLists.txt 76 | - Use `${PROJECT_VERSION}` instead of `${CMAKE_PROJECT_VERSION}` 77 | 78 | ### Fixed 79 | 80 | - Assignment of value instead of comparison (src/xdgdirs.c:35) 81 | 82 | ## [1.0.1] - 2020-12-20 83 | ### Added 84 | 85 | - LGTM configuration 86 | 87 | ### Changed 88 | 89 | - Cosmetic change from `-pedantic` compiler flag to `-Wpedantic` 90 | 91 | ### Fixed 92 | 93 | - Check if `fmt` in `xdgDirs_getenv()` is `NULL` 94 | 95 | ## [1.0.0] - 2020-12-20 96 | ### Added 97 | 98 | - Source code of library 99 | - Simple examples of usage 100 | - Documentation 101 | - README, CHANGELOG and AUTHORS 102 | 103 | ## 104 | [unreleased]: https://github.com/Jorenar/libXDGdirs/compare/v1.1.3...HEAD 105 | [1.1.3]: https://github.com/Jorenar/libXDGdirs/compare/v1.1.2...v1.1.3 106 | [1.1.2]: https://github.com/Jorenar/libXDGdirs/compare/v1.1.1...v1.1.2 107 | [1.1.1]: https://github.com/Jorenar/libXDGdirs/compare/v1.1.0...v1.1.1 108 | [1.1.0]: https://github.com/Jorenar/libXDGdirs/compare/v1.0.5...v1.1.0 109 | [1.0.5]: https://github.com/Jorenar/libXDGdirs/compare/v1.0.4...v1.0.5 110 | [1.0.4]: https://github.com/Jorenar/libXDGdirs/compare/v1.0.3...v1.0.4 111 | [1.0.3]: https://github.com/Jorenar/libXDGdirs/compare/v1.0.2...v1.0.3 112 | [1.0.2]: https://github.com/Jorenar/libXDGdirs/compare/v1.0.1...v1.0.2 113 | [1.0.1]: https://github.com/Jorenar/libXDGdirs/compare/v1.0.0...v1.0.1 114 | [1.0.0]: https://github.com/Jorenar/libXDGdirs/releases/tag/v1.0.0 115 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | 3 | project(libXDGdirs 4 | DESCRIPTION "An implementation of XDG Base Directory Specification" 5 | VERSION "1.1.3" 6 | LANGUAGES C) 7 | 8 | set(XDGDIRS "XDGdirs") 9 | 10 | set(CMAKE_DISABLE_SOURCE_CHANGES ON) 11 | set(CMAKE_DISABLE_IN_SOURCE_BUILD ON) 12 | 13 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib/static) 14 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib/dynamic) 15 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) 16 | 17 | # compile_commands.json 18 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 19 | if(NOT EXISTS "${CMAKE_SOURCE_DIR}/compile_commands.json") 20 | file(RELATIVE_PATH buildRelPath "${CMAKE_SOURCE_DIR}" "${CMAKE_BINARY_DIR}") 21 | execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink 22 | "${buildRelPath}/compile_commands.json" 23 | "${CMAKE_SOURCE_DIR}/compile_commands.json") 24 | endif() 25 | 26 | set(CMAKE_C_STANDARD 99) 27 | 28 | set(CMAKE_C_FLAGS_DEBUG "-O0 -g -Wall -Wextra -Wpedantic -DDEBUG") 29 | set(CMAKE_C_FLAGS_RELEASE "-O2 -DNDEBUG") 30 | 31 | add_subdirectory(src/) 32 | add_subdirectory(docs/) 33 | 34 | configure_file(${CMAKE_PROJECT_NAME}.pc.in 35 | lib/pkgconfig/${CMAKE_PROJECT_NAME}.pc @ONLY) 36 | 37 | if(CMAKE_BUILD_TYPE STREQUAL "Debug") 38 | include(CTest) 39 | add_subdirectory(tests/) 40 | add_subdirectory(examples/) 41 | endif() 42 | 43 | install(TARGETS "${XDGDIRS}") 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2020-2024 Jorengarenar 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | libXDGdirs 2 | ========== 3 | 4 | [![Codacy Badge](https://app.codacy.com/project/badge/Grade/547fc2ec9e314ad593c14d84a7d4fab5)](https://app.codacy.com/gh/Jorenar/libXDGdirs/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade) 5 | [![CodeFactor](https://www.codefactor.io/repository/github/jorenar/libxdgdirs/badge)](https://www.codefactor.io/repository/github/jorenar/libxdgdirs) 6 | [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=Jorenar_libXDGdirs&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=Jorenar_libXDGdirs) 7 | [![License](https://img.shields.io/github/license/Jorenar/libXDGdirs)](https://github.com/Jorenar/libXDGdirs/blob/master/LICENSE) 8 | [![GitHub release (latest SemVer including pre-releases)](https://img.shields.io/github/v/release/Jorenar/libXDGdirs?include_prereleases&sort=semver)](https://github.com/Jorenar/libXDGdirs/releases) 9 | [![AUR version](https://img.shields.io/aur/version/libxdgdirs)](https://aur.archlinux.org/packages/libxdgdirs) 10 | 11 | An implementation of [XDG Base Directory Specification](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html) 12 | 13 | _XDGBDS_ defines four categories of dotfiles and the corresponding directories 14 | in user's home directory that should be used for those. The categories are 15 | cache, configuration, data, state and runtime files. 16 | 17 | All those locations have corresponding `XDG_*` environment variables. 18 | 19 | All paths set in them must be absolute. 20 | If a relative path is encountered, the path is considered invalid and `NULL` is assigned. 21 | 22 | ## Build 23 | 24 | ### Download 25 | 26 | Download the latest version from [release page](https://github.com/Jorenar/libXDGdirs/releases) 27 | or clone Git repository for latest: 28 | ```sh 29 | git clone https://github.com/Jorenar/libXDGdirs.git 30 | cd libXDGdirs 31 | ``` 32 | 33 | ### Build 34 | ```sh 35 | cmake -B build/ -DCMAKE_BUILD_TYPE=Release 36 | cmake --build build/ --config=Release 37 | ``` 38 | 39 | ### Install 40 | ```sh 41 | cmake --install build/ 42 | ``` 43 | 44 | ## Documentation 45 | 46 | [Documentation](https://jorenar.github.io/libXDGdirs) generated from **latest commit** using [Doxygen](https://www.doxygen.nl) 47 | 48 | ## Usage 49 | 50 | Refer to [topics](https://jorenar.github.io/libXDGdirs/topics.html) 51 | and [examples](https://jorenar.github.io/libXDGdirs/examples.html) 52 | in [documentation](https://jorenar.github.io/libXDGdirs) 53 | 54 | ## Read more 55 | 56 | * [XDG Base Directory Specification](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html) 57 | * [XDG Base Directory - ArchWiki](https://wiki.archlinux.org/index.php/XDG_Base_Directory) 58 | * [XDGBaseDirectorySpecification - Debian Wiki](https://wiki.debian.org/XDGBaseDirectorySpecification) 59 | -------------------------------------------------------------------------------- /docs/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(Doxygen) 2 | if(NOT DOXYGEN_FOUND) 3 | message("Doxygen needs to be installed to generate documentation") 4 | return() 5 | endif(NOT DOXYGEN_FOUND) 6 | 7 | set(DOXYGEN_QUIET YES) 8 | 9 | set(DOXYGEN_EXCLUDE_PATTERNS 10 | */build/* 11 | */extern/* 12 | ) 13 | 14 | find_package(Git) 15 | if(GIT_EXECUTABLE) 16 | execute_process( 17 | COMMAND ${GIT_EXECUTABLE} describe --tags --dirty --match "v*" 18 | OUTPUT_STRIP_TRAILING_WHITESPACE 19 | OUTPUT_VARIABLE DOXYGEN_PROJECT_NUMBER 20 | ) 21 | endif() 22 | 23 | if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug") 24 | list(APPEND DOXYGEN_EXCLUDE_PATTERNS */src/xdgdirs.c) 25 | endif() 26 | 27 | set(DOXYGEN_HTML_EXTRA_STYLESHEET custom.css) 28 | set(DOXYGEN_LAYOUT_FILE layout.xml) 29 | 30 | set(DOXYGEN_EXAMPLE_PATH ${PROJECT_SOURCE_DIR}/examples) 31 | set(DOXYGEN_USE_MDFILE_AS_MAINPAGE ${CMAKE_SOURCE_DIR}/README.md) 32 | 33 | set(DOXYGEN_HTML_COLORSTYLE TOGGLE) 34 | 35 | set(DOXYGEN_ALPHABETICAL_INDEX NO) 36 | set(DOXYGEN_ALWAYS_DETAILED_SEC NO) 37 | set(DOXYGEN_GENERATE_TREEVIEW YES) 38 | set(DOXYGEN_OPTIMIZE_OUTPUT_FOR_C YES) 39 | set(DOXYGEN_REPEAT_BRIEF NO) 40 | set(DOXYGEN_SOURCE_BROWSER YES) 41 | set(DOXYGEN_STRIP_FROM_PATH "${CMAKE_SOURCE_DIR}") 42 | 43 | set(DOXYGEN_EXTRACT_ALL YES) 44 | set(DOXYGEN_EXTRACT_STATIC YES) 45 | 46 | set(DOXYGEN_PREDEFINED DOXYGEN) 47 | set(DOXYGEN_EXCLUDE_SYMBOLS DOXYGEN_*) 48 | set(DOXYGEN_EXPAND_AS_DEFINED DOXYGEN_UNNAMED) 49 | set(DOXYGEN_MACRO_EXPANSION YES) 50 | 51 | doxygen_add_docs(docs ${CMAKE_SOURCE_DIR} ALL) 52 | -------------------------------------------------------------------------------- /docs/custom.css: -------------------------------------------------------------------------------- 1 | div.fragment { 2 | padding: 1em; 3 | } 4 | 5 | div.line { 6 | min-height: 1.2em; 7 | } 8 | 9 | div.fragment > .line:first-child:empty { 10 | display: none; 11 | } 12 | 13 | code { 14 | padding: 0 0.1em; 15 | } 16 | 17 | p { 18 | max-width: 100ch; 19 | text-align: justify; 20 | } 21 | -------------------------------------------------------------------------------- /docs/layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include_directories(${PROJECT_SOURCE_DIR}/src) 2 | 3 | file(GLOB sources RELATIVE ${PROJECT_SOURCE_DIR}/examples *.c) 4 | foreach(source ${sources}) 5 | string(REPLACE ".c" "" bin ${source}) 6 | add_executable(${bin} ${source}) 7 | target_link_libraries(${bin} ${XDGDIRS}) 8 | endforeach() 9 | -------------------------------------------------------------------------------- /examples/compilation.dox: -------------------------------------------------------------------------------- 1 | /// @example compilation.dox 2 | # Compile your program passing -lXDGdirs: 3 | cc main.c -o app -lXDGdirs 4 | 5 | # or use pkg-config: 6 | eval cc main.c $(pkg-config --cflags --libs libXDGdirs) 7 | -------------------------------------------------------------------------------- /examples/custom.c: -------------------------------------------------------------------------------- 1 | /** @example custom.c 2 | * @code{.unparsed} 3 | * -- POSSIBLE OUTPUT -- 4 | * 5 | * XDG_MUSIC_DIR: /home/user/music 6 | * @endcode 7 | */ 8 | /// @cond 9 | #include 10 | #include 11 | 12 | int main(void) 13 | { 14 | printf("XDG_MUSIC_DIR: %s\n", xdgCustomVar("MUSIC")); 15 | return 0; 16 | } 17 | /// @endcond 18 | -------------------------------------------------------------------------------- /examples/lists.c: -------------------------------------------------------------------------------- 1 | /** @example lists.c 2 | * @code{.unparsed} 3 | * -- POSSIBLE OUTPUT -- 4 | * 5 | * /usr/local/share:/usr/share 6 | * 2 7 | * /usr/local/share 8 | * /usr/share 9 | * 10 | * /etc/xdg 11 | * 1 12 | * /etc/xdg 13 | * @endcode 14 | */ 15 | /// @cond 16 | #include 17 | #include 18 | 19 | int main() 20 | { 21 | // XDG_DATA_DIRS --------------------------------------- 22 | xdgDirsList foo = *xdgDataDirs(); 23 | puts(foo.raw); 24 | printf("%zd\n", foo.size); 25 | for (int i = 0; foo.list[i] != NULL; ++i) { 26 | puts(foo.list[i]); 27 | } 28 | 29 | // XDG_CONFIG_DIRS ------------------------------------- 30 | xdgDirsList* bar = xdgConfigDirs(); 31 | printf("\n%s\n%zd\n", bar->raw, bar->size); 32 | for (int i = 0; bar->list[i] != NULL; ++i) { 33 | puts(bar->list[i]); 34 | } 35 | 36 | // free memory ----------------------------------------- 37 | xdgDirs_clear(); 38 | 39 | return 0; 40 | } 41 | /// @endcond 42 | -------------------------------------------------------------------------------- /examples/print.c: -------------------------------------------------------------------------------- 1 | /** @example print.c 2 | * @code{.unparsed} 3 | * -- POSSIBLE OUTPUT -- 4 | * 5 | * XDG_DATA_HOME /home/user/.local/share 6 | * XDG_CONFIG_HOME /home/user/.config 7 | * XDG_CACHE_HOME /home/user/.cache 8 | * XDG_RUNTIME_DIR /run/user/1000 9 | * XDG_DATA_DIRS /usr/local/share:/usr/share 10 | * XDG_CONFIG_DIRS /etc/xdg 11 | * @endcode 12 | */ 13 | /// @cond 14 | #include 15 | #include 16 | 17 | int main() 18 | { 19 | const char* fmt = "%-20s %s\n"; 20 | printf(fmt, "XDG_DATA_HOME", xdgDataHome()); 21 | printf(fmt, "XDG_CONFIG_HOME", xdgConfigHome()); 22 | printf(fmt, "XDG_CACHE_HOME", xdgCacheHome()); 23 | printf(fmt, "XDG_RUNTIME_DIR", xdgRuntimeDir()); 24 | printf(fmt, "XDG_DATA_DIRS", xdgDataDirs()->raw); 25 | printf(fmt, "XDG_CONFIG_DIRS", xdgConfigDirs()->raw); 26 | xdgDirs_clear(); 27 | return 0; 28 | } 29 | /// @endcond 30 | -------------------------------------------------------------------------------- /examples/ver.c: -------------------------------------------------------------------------------- 1 | /** @example ver.c 2 | * @code{.unparsed} 3 | * -- POSSIBLE OUTPUT -- 4 | * 5 | * 1.0.0 6 | * 0.7 7 | * @endcode 8 | */ 9 | /// @cond 10 | #include 11 | #include 12 | 13 | int main() 14 | { 15 | puts(XDGDIRS_VER); 16 | puts(XDGBDS_VER); 17 | return 0; 18 | } 19 | /// @endcond 20 | -------------------------------------------------------------------------------- /libXDGdirs.pc.in: -------------------------------------------------------------------------------- 1 | prefix="@CMAKE_INSTALL_PREFIX@" 2 | exec_prefix="${prefix}" 3 | includedir="${prefix}/include" 4 | libdir="${exec_prefix}/lib" 5 | 6 | Name: @CMAKE_PROJECT_NAME@ 7 | Description: @CMAKE_PROJECT_DESCRIPTION@ 8 | Version: @CMAKE_PROJECT_VERSION@ 9 | Cflags: -I${includedir} 10 | Libs: -L${libdir} -l@XDGDIRS@ 11 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CMAKE_C_OUTPUT_EXTENSION_REPLACE YES) 2 | add_library("${XDGDIRS}" STATIC xdgdirs.c) 3 | set_target_properties("${XDGDIRS}" PROPERTIES PUBLIC_HEADER "src/xdgdirs.h") 4 | -------------------------------------------------------------------------------- /src/xdgdirs.c: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * Copyright 2020-2024 Jorengarenar 3 | */ 4 | 5 | #include "xdgdirs.h" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | /// @addtogroup DEV ~dev 13 | /// @brief Additional documentation for contibutors 14 | /// @details Generated with debug version 15 | /// @{ 16 | 17 | /// @name Helpers 18 | /// @{ 19 | 20 | static const char* xdgDirs_strdup(const char* str) 21 | { 22 | if (str == NULL) { 23 | return NULL; 24 | } 25 | size_t len = strlen(str) + 1; 26 | char* out = malloc(len * sizeof(*out)); 27 | return (out ? memcpy(out, str, len) : NULL); 28 | } 29 | 30 | static void xdgDirs_getenv(const char** ptr, const char* var, const char* fmt, ...) 31 | { 32 | const char* temp = getenv(var); 33 | if (temp != NULL && temp[0] == '/') { 34 | *ptr = xdgDirs_strdup(temp); 35 | return; 36 | } 37 | 38 | if (fmt == NULL || fmt[0] == '\0') { 39 | *ptr = NULL; 40 | return; 41 | } 42 | 43 | va_list ap; 44 | va_start(ap, fmt); 45 | 46 | char fallback[BUFSIZ]; 47 | vsnprintf(fallback, BUFSIZ, fmt, ap); 48 | 49 | *ptr = (fallback[0] == '/' ? xdgDirs_strdup(fallback) : NULL); 50 | 51 | va_end(ap); 52 | } 53 | 54 | static void xdgDirs_free(const char** str, struct xdgDirsList_t* li) 55 | { 56 | if (str && *str) { 57 | free((void*)(*str)); 58 | *str = NULL; 59 | } else if (li) { 60 | xdgDirs_free(&li->raw, NULL); 61 | for (size_t i = 0; li->list[i]; ++i) { 62 | free((void*)li->list[i]); 63 | } 64 | free(li->list); 65 | li->list = NULL; 66 | li->size = 0; 67 | } 68 | } 69 | 70 | static void xdgDirs_genList(struct xdgDirsList_t* li) 71 | { 72 | li->size = 1; 73 | char* raw = (char*)xdgDirs_strdup(li->raw); 74 | for (size_t i = 0; raw[i]; ++i) { 75 | if (raw[i] == ':' && raw[i+1] != ':' && raw[i+1] != '\0') { 76 | li->size += 1; 77 | } 78 | } 79 | 80 | li->list = malloc((li->size + 1) * sizeof(*(li->list))); 81 | 82 | const char* v = strtok(raw, ":"); 83 | for (size_t i = 0; i < li->size && v != NULL; ++i) { 84 | li->list[i] = xdgDirs_strdup(v); 85 | v = strtok(NULL, ":"); 86 | } 87 | 88 | free(raw); 89 | 90 | li->list[li->size] = NULL; 91 | } 92 | 93 | /// @} 94 | 95 | /// @name Data cache management 96 | /// @{ 97 | 98 | #if DOXYGEN 99 | # define DOXYGEN_UNNAMED xdgDirs_cache_t 100 | #else 101 | # define DOXYGEN_UNNAMED 102 | #endif 103 | 104 | /** @var xdgDirs_cache 105 | * @brief Container for cached values from environment variables 106 | * @details 107 | * The results of @ref CACHE_MANAGEMENT functions.\n 108 | * Check xdgDirs_cache_t 109 | */ 110 | 111 | static struct DOXYGEN_UNNAMED { 112 | short initialized; 113 | struct /* user */ { 114 | const char* data; ///< @c $XDG_DATA_HOME 115 | const char* state; ///< @c $XDG_STATE_HOME 116 | const char* config; ///< @c $XDG_CONFIG_HOME 117 | const char* cache; ///< @c $XDG_CACHE_HOME 118 | const char* runtime; ///< @c $XDG_RUNTIME_DIR 119 | } user; ///< User directories 120 | struct /* system */ { 121 | struct xdgDirsList_t data; ///< @c $XDG_DATA_DIRS 122 | struct xdgDirsList_t config; ///< @c $XDG_CONFIG_DIRS 123 | } system; ///< System directories 124 | } xdgDirs_cache = { .initialized = 0 }; 125 | 126 | #undef DOXYGEN_UNNAMED 127 | 128 | void xdgDirs_clear(void) 129 | { 130 | if (xdgDirs_cache.initialized == 0) { 131 | return; 132 | } 133 | 134 | xdgDirs_free(&xdgDirs_cache.user.config, NULL); 135 | xdgDirs_free(&xdgDirs_cache.user.data, NULL); 136 | xdgDirs_free(&xdgDirs_cache.user.state, NULL); 137 | xdgDirs_free(&xdgDirs_cache.user.cache, NULL); 138 | xdgDirs_free(&xdgDirs_cache.user.runtime, NULL); 139 | 140 | xdgDirs_free(NULL, &xdgDirs_cache.system.data); 141 | xdgDirs_free(NULL, &xdgDirs_cache.system.config); 142 | 143 | xdgDirs_cache.initialized = 0; 144 | } 145 | 146 | int xdgDirs_init(void) 147 | { 148 | if (xdgDirs_cache.initialized == 1) { 149 | return 0; 150 | } 151 | 152 | xdgDirs_clear(); 153 | 154 | const char* home = getenv("HOME"); 155 | 156 | if (home == NULL) { 157 | return 1; 158 | } 159 | 160 | xdgDirs_getenv(&xdgDirs_cache.user.data, "XDG_DATA_HOME", "%s%s", home, "/.local/share"); 161 | xdgDirs_getenv(&xdgDirs_cache.user.state, "XDG_STATE_HOME", "%s%s", home, "/.local/state"); 162 | xdgDirs_getenv(&xdgDirs_cache.user.config, "XDG_CONFIG_HOME", "%s%s", home, "/.config"); 163 | xdgDirs_getenv(&xdgDirs_cache.user.cache, "XDG_CACHE_HOME", "%s%s", home, "/.cache"); 164 | xdgDirs_getenv(&xdgDirs_cache.user.runtime, "XDG_RUNTIME_DIR", ""); 165 | 166 | xdgDirs_getenv(&xdgDirs_cache.system.data.raw, "XDG_DATA_DIRS", "/usr/local/share:/usr/share"); 167 | xdgDirs_genList(&xdgDirs_cache.system.data); 168 | 169 | xdgDirs_getenv(&xdgDirs_cache.system.config.raw, "XDG_CONFIG_DIRS", "/etc/xdg"); 170 | xdgDirs_genList(&xdgDirs_cache.system.config); 171 | 172 | xdgDirs_cache.initialized = 1; 173 | 174 | return 0; 175 | } 176 | 177 | void xdgDirs_refresh(void) 178 | { 179 | xdgDirs_cache.initialized = -1; 180 | xdgDirs_init(); 181 | } 182 | 183 | /// @} 184 | 185 | /// @name Reading environment variables 186 | /// @{ 187 | 188 | #define XDGDIRS_RETURN(var) \ 189 | do { \ 190 | if (xdgDirs_init() != 0) { \ 191 | return NULL; \ 192 | } \ 193 | return var; \ 194 | } while (0) 195 | 196 | // User directories {{{1 197 | 198 | const char* xdgDataHome(void) 199 | { 200 | XDGDIRS_RETURN(xdgDirs_cache.user.data); 201 | } 202 | 203 | const char* xdgStateHome(void) 204 | { 205 | XDGDIRS_RETURN(xdgDirs_cache.user.state); 206 | } 207 | 208 | const char* xdgConfigHome(void) 209 | { 210 | XDGDIRS_RETURN(xdgDirs_cache.user.config); 211 | } 212 | 213 | const char* xdgCacheHome(void) 214 | { 215 | XDGDIRS_RETURN(xdgDirs_cache.user.cache); 216 | } 217 | 218 | const char* xdgRuntimeDir(void) 219 | { 220 | XDGDIRS_RETURN(xdgDirs_cache.user.runtime); 221 | } 222 | 223 | // System directories {{{1 224 | 225 | xdgDirsList* xdgDataDirs(void) 226 | { 227 | XDGDIRS_RETURN(&xdgDirs_cache.system.data); 228 | } 229 | 230 | xdgDirsList* xdgConfigDirs(void) 231 | { 232 | XDGDIRS_RETURN(&xdgDirs_cache.system.config); 233 | } 234 | 235 | // }}}1 236 | 237 | #undef XDGDIRS_RETURN 238 | 239 | const char* xdgCustomVar(const char* custom) 240 | { 241 | char var[1000]; 242 | snprintf(var, 1000, "%s%s%s", "XDG_", custom, "_DIR"); 243 | const char* x = getenv(var); 244 | return x == NULL ? "" : x; 245 | } 246 | 247 | /// @} 248 | 249 | /// @} 250 | 251 | // vim: fdl=1 252 | -------------------------------------------------------------------------------- /src/xdgdirs.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * Copyright 2020-2024 Jorengarenar 3 | */ 4 | 5 | #ifndef XDGDIRS_H_ 6 | #define XDGDIRS_H_ 7 | 8 | #ifdef __cplusplus 9 | extern "C" { 10 | #endif 11 | 12 | #include 13 | 14 | /** @def XDGDIRS_VER 15 | * @brief Version of libXDGdirs 16 | * @details 17 | * Adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) 18 | */ 19 | #define XDGDIRS_VER "1.1.3" 20 | 21 | /// Version of XDG Base Directory specification implemented in this library 22 | #define XDGBDS_VER "0.8" 23 | 24 | /// @brief Container for environment variables which are colon separated lists 25 | struct xdgDirsList_t { 26 | const char* raw; ///< Raw value of environment variable 27 | const char** list; ///< Null terminated array containing entries from environment variable 28 | size_t size; ///< Number of entries in environment variable 29 | }; 30 | 31 | typedef const struct xdgDirsList_t xdgDirsList; 32 | 33 | /// @defgroup CACHE_MANAGEMENT Data cache management 34 | /// @{ 35 | 36 | /** @brief Explicitly read variables before first use 37 | * @details 38 | * Will be used automatically with first call to any function from 39 | * @ref VARIABLE_GETTERS 40 | * 41 | * Call it if you want to have all data initialized before first use 42 | * 43 | * Returns 0 on success, positive value otherwise 44 | */ 45 | int xdgDirs_init(void); 46 | 47 | /** @brief Clear cached data 48 | * @details 49 | * Deinitializes cached data and frees memory 50 | * 51 | * Call when XDGBDS is no longer needed (presumably at the end of program) 52 | */ 53 | void xdgDirs_clear(void); 54 | 55 | /** @brief Read environment variables again 56 | * @details 57 | * Clear cache with xdgDirs_clear() and initializes again with xdgDirs_init() 58 | */ 59 | void xdgDirs_refresh(void); 60 | 61 | /// @} 62 | 63 | /// @defgroup VARIABLE_GETTERS Reading environment variables 64 | /// @{ 65 | 66 | /// @defgroup USER_DIRS User directories 67 | /// @brief Base directories to which user-specific files should be written 68 | /// @{ 69 | 70 | /** @brief Value of @c $XDG_DATA_HOME 71 | * @details 72 | * Base directory for user-specific data files (analogous to @c /usr/share) 73 | * @return a path as described by the standards or NULL 74 | */ 75 | const char* xdgDataHome(void); 76 | 77 | /** @brief Value of @c $XDG_STATE_HOME 78 | * @details 79 | * Base directory for user-specific state files 80 | * @return a path as described by the standards or NULL 81 | */ 82 | const char* xdgStateHome(void); 83 | 84 | /** @brief Value of @c $XDG_CONFIG_HOME 85 | * @details 86 | * Base directory for user-specific configuration files (analogous to @c /etc) 87 | * @return a path as described by the standards or NULL 88 | */ 89 | const char* xdgConfigHome(void); 90 | 91 | /** @brief Value of @c $XDG_CACHE_HOME 92 | * @details 93 | * Base directory for user-specific non-essential data files (analogous to @c /var/cache) 94 | * @return a path as described by the standards or NULL 95 | */ 96 | const char* xdgCacheHome(void); 97 | 98 | /** @brief Value of @c $XDG_RUNTIME_DIR 99 | * @details 100 | * Base directory for user-specific non-essential runtime files (including file 101 | * objects such as sockets and named pipes) 102 | * @return a path as described by the standards or NULL 103 | */ 104 | const char* xdgRuntimeDir(void); 105 | 106 | /// @} 107 | 108 | /// @defgroup SYS_DIRS System directories 109 | /// @brief Sets of preference ordered base directories relative to which files should be searched 110 | /// @{ 111 | 112 | /** @brief Value of @c $XDG_DATA_DIRS 113 | * @details 114 | * Preference-ordered set of base directories to search for data files 115 | * in addition to the @c $XDG_DATA_HOME base directory 116 | * 117 | * See lists.c for example 118 | * @return pointer to `const struct xdgDirsList_t` 119 | */ 120 | xdgDirsList* xdgDataDirs(void); 121 | 122 | /** @brief Value of @c $XDG_CONFIG_DIRS 123 | * @details 124 | * Preference-ordered set of base directories to search for configuration 125 | * files in addition to the @c $XDG_CONFIG_HOME base directory 126 | * @return pointer to `const struct xdgDirsList_t` 127 | */ 128 | xdgDirsList* xdgConfigDirs(void); 129 | 130 | /// @} 131 | 132 | /** @brief User definied custom XDG variables 133 | * @details 134 | * Environment variables of pattern @c XDG_*_DIR 135 | * 136 | * Those are not cached 137 | * @returns a value of @c XDG_*_DIR variable or NULL 138 | */ 139 | const char* xdgCustomVar(const char* custom); 140 | 141 | /// @} 142 | 143 | #ifdef __cplusplus 144 | } // extern "C" 145 | #endif 146 | 147 | #endif // XDGDIRS_H_ 148 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include_directories(${PROJECT_SOURCE_DIR}/src) 2 | 3 | add_executable(test_bin test.c) 4 | target_link_libraries(test_bin ${XDGDIRS}) 5 | 6 | file(GLOB ins RELATIVE ${PROJECT_SOURCE_DIR}/tests "test[0-9]*") 7 | 8 | foreach(in ${ins}) 9 | add_test( 10 | NAME "${in}" 11 | COMMAND sh ./${in} ${PROJECT_BINARY_DIR}/bin/test_bin 12 | WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/tests) 13 | endforeach() 14 | -------------------------------------------------------------------------------- /tests/test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "xdgdirs.h" 3 | 4 | int main() 5 | { 6 | const char* data_home = xdgDataHome(); 7 | if (data_home) { 8 | puts(data_home); 9 | } 10 | return 0; 11 | } 12 | -------------------------------------------------------------------------------- /tests/test1: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | XDG_DATA_HOME="/aaa" 4 | 5 | expected="$XDG_DATA_HOME" 6 | 7 | if [ "$($1)" != "$expected" ]; then 8 | exit 1 9 | fi 10 | --------------------------------------------------------------------------------