├── .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 | [](https://app.codacy.com/gh/Jorenar/libXDGdirs/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
5 | [](https://www.codefactor.io/repository/github/jorenar/libxdgdirs)
6 | [](https://sonarcloud.io/summary/new_code?id=Jorenar_libXDGdirs)
7 | [](https://github.com/Jorenar/libXDGdirs/blob/master/LICENSE)
8 | [](https://github.com/Jorenar/libXDGdirs/releases)
9 | [](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 |
--------------------------------------------------------------------------------