fat_config 0.7.1 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9402d73a4e16cab2a9088bece9dfa623d406858b5c0ae641df01ee988029e6a1
4
- data.tar.gz: 042c81ad9c917ea1073db02259718a73792dd7e134f8c9c3aa4b60f7d9b756b4
3
+ metadata.gz: 431c883197a326bde2824b5b605b667adede33fcee328575d3c123e6a051918b
4
+ data.tar.gz: f97f4216cfddebe0d5c4b034b387005916c38e3e61d22b57b67de41001c6d367
5
5
  SHA512:
6
- metadata.gz: 9026e4d9b87df9db13e341e5e9af201388335db1a750e7e5d72ddea797787034545bdc03b0358acef9a16ae7fb37e31c7e305f23fc6f1023b5e3acf7fb7f2b72
7
- data.tar.gz: 24b464de194eef9fb81afdcc23afd24dfcf4dcca35b6411d9befed18435dc6c98c3d147c5f6971d586a04e345fdc027be4b34e07fc6f75b6caf47a1ee4da7ccd
6
+ metadata.gz: 756d931f4a0a11b1d235f406b6c8624bd140ed11045a3aa6eeed85edb425d5139648cffc0866d3e59be6b2b73eeecbd689d641c0765d65b9915b0649672b6149
7
+ data.tar.gz: 7b896e62e452f926038b84c65405d10329edcb48c816a257da14025d11e5890bf54c5f1cd9ee40f211c8396b423335e756ea8305355b2c9a62f31a30f57d2db1
data/CHANGELOG.md CHANGED
@@ -1,15 +1,33 @@
1
- - [Version 0.7.1 <span class="timestamp-wrapper"><span class="timestamp">[2026-02-06 Fri]</span></span>](#org48cabe8)
2
- - [Version 0.7.0 <span class="timestamp-wrapper"><span class="timestamp">[2026-02-05 Thu]</span></span>](#orga2f16a3)
1
+ - [Version 0.8.1 <span class="timestamp-wrapper"><span class="timestamp">[2026-11-14 Sat]</span></span>](#orgd39cf28)
2
+ - [Version 0.8.0](#orgd2c1c74)
3
+ - [Version 0.7.1 <span class="timestamp-wrapper"><span class="timestamp">[2026-02-06 Fri]</span></span>](#org1b3a9f5)
4
+ - [Version 0.7.0 <span class="timestamp-wrapper"><span class="timestamp">[2026-02-05 Thu]</span></span>](#orgd5760a0)
3
5
 
4
6
 
5
- <a id="org48cabe8"></a>
7
+ <a id="orgd39cf28"></a>
8
+
9
+ # Version 0.8.1 <span class="timestamp-wrapper"><span class="timestamp">[2026-11-14 Sat]</span></span>
10
+
11
+ - Fix Windows environment-selected config filenames and classic user paths.
12
+ - Use semicolons for Windows XDG\_CONFIG\_DIRS lists, preserving Unix colons and directory precedence.
13
+ - Support Windows paths under test sandbox root prefixes.
14
+
15
+
16
+ <a id="orgd2c1c74"></a>
17
+
18
+ # Version 0.8.0
19
+
20
+ - Allow user to specify an explicit user or system config directory, ignoring XDG and classic convention.
21
+
22
+
23
+ <a id="org1b3a9f5"></a>
6
24
 
7
25
  # Version 0.7.1 <span class="timestamp-wrapper"><span class="timestamp">[2026-02-06 Fri]</span></span>
8
26
 
9
27
  - Improved error diagnostics on ParseError
10
28
 
11
29
 
12
- <a id="orga2f16a3"></a>
30
+ <a id="orgd5760a0"></a>
13
31
 
14
32
  # Version 0.7.0 <span class="timestamp-wrapper"><span class="timestamp">[2026-02-05 Thu]</span></span>
15
33
 
data/CHANGELOG.org CHANGED
@@ -6,6 +6,16 @@
6
6
  user would find useful.
7
7
  5. Give each heading a version number and an inactive date (C-c ! is useful here).
8
8
 
9
+ * Version 0.8.1 [2026-11-14 Sat]
10
+ - Fix Windows environment-selected config filenames and classic user paths.
11
+ - Use semicolons for Windows XDG_CONFIG_DIRS lists, preserving Unix colons
12
+ and directory precedence.
13
+ - Support Windows paths under test sandbox root prefixes.
14
+
15
+ * Version 0.8.0
16
+ - Allow user to specify an explicit user or system config directory, ignoring
17
+ XDG and classic convention.
18
+
9
19
  * Version 0.7.1 [2026-02-06 Fri]
10
20
  - Improved error diagnostics on ParseError
11
21
 
data/README.md CHANGED
@@ -1,26 +1,32 @@
1
- - [Introduction](#orgc4b482f)
2
- - [Installation](#orgbce0696)
3
- - [Usage:](#org7684da4)
4
- - [Following XDG Standards](#orga8e28a3)
5
- - [Following Classic UNIX Standards](#orgd716a45)
6
- - [Available Config File Styles](#org152041e)
7
- - [Hash Keys](#orgb744014)
8
- - [Hash Values](#org02d5c86)
9
- - [YAML](#org73fadf6)
10
- - [TOML](#orge78f21a)
11
- - [JSON](#org22479cd)
12
- - [INI](#org850bdf4)
13
- - [Creating a Reader](#org570f7c5)
14
- - [Calling the `read` method on a `Reader`](#org9637e7b)
1
+ <!-- Generated by gem_docs -->
2
+
3
+ - [Introduction](#orgcd63dc2)
4
+ - [Installation](#org15c7887)
5
+ - [Usage:](#org56d9f5b)
6
+ - [Following XDG Standards](#org3be69dd)
7
+ - [Following Classic UNIX Standards](#org064ccaf)
8
+ - [Explicitly Providing Config Directories](#orge127d5b)
9
+ - [Windows Paths](#org70b57bc)
10
+ - [Available Config File Styles](#org9c70294)
11
+ - [Hash Keys](#org9f8575c)
12
+ - [Hash Values](#orgdba3006)
13
+ - [YAML](#org07f50e6)
14
+ - [TOML](#org3c19011)
15
+ - [JSON](#orgdc9ffb1)
16
+ - [INI](#orgd9553d5)
17
+ - [Creating a Reader](#orgfd67663)
18
+ - [Calling the `read` method on a `Reader`](#orgd2e0f2e)
15
19
  - [Parsing Environment and Command Line Strings](#parsing-environment-and-command-line-strings)
16
- - [Development](#orgda3ea75)
17
- - [Contributing](#orgfb56ba2)
18
- - [License](#org0377369)
20
+ - [Development](#org247b85b)
21
+ - [Contributing](#org52f251b)
22
+ - [License](#orgd18d129)
23
+
24
+ Version: 0.8.1
19
25
 
20
26
  [![img](https://github.com/ddoherty03/fat_config/actions/workflows/main.yml/badge.svg?branch=master)](https://github.com/ddoherty03/fat_config/actions/workflows/main.yml)
21
27
 
22
28
 
23
- <a id="orgc4b482f"></a>
29
+ <a id="orgcd63dc2"></a>
24
30
 
25
31
  # Introduction
26
32
 
@@ -29,7 +35,7 @@ Allowing a user to configure an application to change its behavior at runtime ca
29
35
  `FatConfig` eliminates the tedium of reading configuration files and the environment to populate a Hash of configuration settings. You need only define a `FatConfig::Reader` and call its `#read` method to look for, read, translate, and merge any config files into a single Hash that encapsulates all the files in the proper priority. It can be set to read `YAML`, `TOML`, `JSON`, or `INI` config files.
30
36
 
31
37
 
32
- <a id="orgbce0696"></a>
38
+ <a id="org15c7887"></a>
33
39
 
34
40
  # Installation
35
41
 
@@ -46,7 +52,7 @@ gem install fat_config
46
52
  ```
47
53
 
48
54
 
49
- <a id="org7684da4"></a>
55
+ <a id="org56d9f5b"></a>
50
56
 
51
57
  # Usage:
52
58
 
@@ -64,21 +70,21 @@ config = reader.read
64
70
  The `reader.read` method will parse the config files (by default assumed to be YAML files), config environment variable, and optional command-line parameters and return the composite config as a Hash.
65
71
 
66
72
 
67
- <a id="orga8e28a3"></a>
73
+ <a id="org3be69dd"></a>
68
74
 
69
75
  ## Following XDG Standards
70
76
 
71
77
  By default, `FatConfig::Reader#read` follows the [XDG Desktop Standards](https://specifications.freedesktop.org/basedir-spec/latest/), reading configuration settings for a hypothetical application called `myapp` from the following locations, from lowest priority to highest:
72
78
 
73
79
  1. If the environment variable `MYAPP_SYS_CONFIG` is set to the name of a file, it will look in that file for any system-level config file.
74
- 2. If the environment variable `MYAPP_SYS_CONFIG` is NOT set, it will read any system-level config file from `/etc/xdg/myapp` or, if the `XDG_CONFIG_DIRS` environment variable is set to a list of colon-separated directories, it will look in each of those instead of `/etc/xdg` for config directories called `myapp`. If more than one `XDG_CONFIG_DIRS` is given, they are treated as listed in order of precedence, so the first-listed directory will be given priority over later ones. All such directories will be read, and any config file found will be merged into the resulting Hash, but they will be visited in reverse order so that the earlier-named directories override the earlier ones.
80
+ 2. If the environment variable `MYAPP_SYS_CONFIG` is NOT set, it will read any system-level config file from `/etc/xdg/myapp` or, if the `XDG_CONFIG_DIRS` environment variable is set to a list of directories separated by `File::PATH_SEPARATOR` (colon on Unix, semicolon on Windows), it will look in each of those instead of `/etc/xdg` for config directories called `myapp`. If more than one `XDG_CONFIG_DIRS` is given, they are treated as listed in order of precedence, so the first-listed directory will be given priority over later ones. All such directories will be read, and any config file found will be merged into the resulting Hash, but they will be visited in reverse order so that the earlier-named directories override the earlier ones.
75
81
  3. If the environment variable `MYAPP_CONFIG` is set to a file name, it will look in that file any user-level config file.
76
82
  4. If the environment variable `MYAPP_CONFIG` is NOT set, it will read any user-level config file from `$HOME/.config/myapp` or, if the `XDG_CONFIG_HOME` environment variable is set to an alternative directory, it will look in `XDG_CONFIG_HOME/.config` for a config directory called 'myapp'. Note that in this case, `XDG_CONFIG_HOME` is intended to contain the name of a single directory, not a list of directories as with the system-level config files.
77
83
  5. It will then merge in any options set in the environment variable `MYAPP_OPTIONS`, overriding any conflicting settings gotten from reading the system- and user-level files. It will interpret the String from the environment variable as discussed below in [Parsing Environment and Command Line Strings](#parsing-environment-and-command-line-strings).
78
84
  6. Finally, it will merge in any options given in the optional `command_line:` named parameter to the `#read` method. That parameter can either be a `Hash` or a `String`. If it is a `String`, it is interpreted the same way as the environment variable `MYAPP_OPTIONS` as explained below in [Parsing Environment and Command Line Strings](#parsing-environment-and-command-line-strings); if it is a `Hash`, it is used directly and merged into the hash returned from the prior methods.
79
85
 
80
86
 
81
- <a id="orgd716a45"></a>
87
+ <a id="org064ccaf"></a>
82
88
 
83
89
  ## Following Classic UNIX Standards
84
90
 
@@ -101,21 +107,43 @@ With the optional `:xdg` keyword parameter to `FatConfig::Reader#read` set to `f
101
107
  6. Finally, it will merge in any options given in the optional `command_line:` named parameter to the `#read` method. That parameter can either be a `Hash` or a `String`. If it is a `String`, it will interpret the string as explained below in [Parsing Environment and Command Line Strings](#parsing-environment-and-command-line-strings); if it is a `Hash`, it is used directly and merged into the hash returned from the prior methods.
102
108
 
103
109
 
104
- <a id="org152041e"></a>
110
+ <a id="orge127d5b"></a>
111
+
112
+ ## Explicitly Providing Config Directories
113
+
114
+ When the `Reader` is created, the user can give an explicit `user_dir:`, `sys_dir:` parameter or both. If given, the XDG or classic conventions for locating config files are ignored regardless of the setting of the `xdg:` parameter.
115
+
116
+
117
+ <a id="org70b57bc"></a>
118
+
119
+ ## Windows Paths
120
+
121
+ Native Windows Ruby accepts drive-qualified filenames and backslashes for `APPNAME_CONFIG` and `APPNAME_SYS_CONFIG`, as well as explicit `user_dir:` and `sys_dir:` paths. Paths containing spaces need no extra quoting inside the Ruby string or environment variable value. UNC filenames retain their server and share prefix.
122
+
123
+ On Windows, separate `XDG_CONFIG_DIRS` entries with semicolons, for example `C:/Company/config;D:/Shared/config`. The first directory has highest priority. Unix continues to use colons. `XDG_CONFIG_HOME` is always a single directory.
124
+
125
+ Default locations remain `/etc`, `/etc/xdg`, and `~/.config`; FatConfig does not automatically select AppData or ProgramData. Set explicit directories or environment variables when those locations are desired.
126
+
127
+ An empty or nil `root_prefix:` leaves expanded filenames intact. A nonempty prefix is intended for test sandboxes: on Windows, `C:/Users/me/config.yml` maps to `<root_prefix>/Users/me/config.yml`. Drive letters are removed only when applying a sandbox prefix, so different drives share the sandbox root. Explicit `user_dir:` and `sys_dir:` continue to bypass `root_prefix:`.
128
+
129
+ **TOML is currently unsupported on native Windows.** The `tomlib 0.7.3` dependency can crash the Ruby process on malformed TOML, rather than raising a parse error. Use YAML, JSON, or INI on Windows until the dependency is fixed.
130
+
131
+
132
+ <a id="org9c70294"></a>
105
133
 
106
134
  ## Available Config File Styles
107
135
 
108
136
  `FatConfig::Reader.new` takes the optional keyword argument, `:style`, to indicate what style to use for config files. It can be one of:
109
137
 
110
138
  - **`yaml`:** See [YAML Specs](https://yaml.org/spec/1.2.2/),
111
- - **`toml`:** See [TOML Specs](https://toml.io/en/),
139
+ - **`toml`:** See [TOML Specs](https://toml.io/en/) (currently unsupported on native Windows),
112
140
  - **`json`:** See [JSON Specs](https://datatracker.ietf.org/doc/html/rfc8259), or
113
141
  - **`ini`:** See [INI File on Wikipedia](https://en.wikipedia.org/wiki/INI_file)
114
142
 
115
143
  By default, the style is `yaml`. Note that the style only pertains to the syntax of on-disk configuration files. Configuration can also be set by an environment variable, `MYAPP_OPTIONS` and by a command-line string optionally provided to the `#read` method. Those are simple parsers that parse strings of option settings as explained below. See, [Parsing Environment and Command Line Strings](#parsing-environment-and-command-line-strings).
116
144
 
117
145
 
118
- <a id="orgb744014"></a>
146
+ <a id="org9f8575c"></a>
119
147
 
120
148
  ## Hash Keys
121
149
 
@@ -124,14 +152,14 @@ Any keys that are Strings will be converted to a symbol, using the names given i
124
152
  Keys that are not Strings will be left alone, so that, for example, you might have Integer keys, which may be useful below the top level.
125
153
 
126
154
 
127
- <a id="org02d5c86"></a>
155
+ <a id="orgdba3006"></a>
128
156
 
129
157
  ## Hash Values
130
158
 
131
159
  Whether the values of the returned Hash will be 'deserialized' into a Ruby object is controlled by the style of the configuration files.
132
160
 
133
161
 
134
- <a id="org73fadf6"></a>
162
+ <a id="org07f50e6"></a>
135
163
 
136
164
  ### YAML
137
165
 
@@ -148,7 +176,7 @@ The `:yaml` style deserializes the following types:
148
176
  - Date, DateTime, and Time, which FatConfig adds to the foregoing default types deserialized by the default YAML library.
149
177
 
150
178
 
151
- <a id="orge78f21a"></a>
179
+ <a id="org3c19011"></a>
152
180
 
153
181
  ### TOML
154
182
 
@@ -156,15 +184,15 @@ The `:toml` style deserializes the following types:
156
184
 
157
185
  - TrueClass (exactly the string 'true')
158
186
  - FalseClass (exactly the string 'false')
159
- - Integer (when it looks like an whole number or 0x&#x2026; or 0o&#x2026; hex or octal)
187
+ - Integer (when it looks like an whole number or 0x&hellip; or 0o&hellip; hex or octal)
160
188
  - Float (when it looks like an decimal number)
161
189
  - String (only if enclosed in single- or double-quotes)
162
- - Array (when sub-elements enclosed in [&#x2026;], each typed by these rules)
190
+ - Array (when sub-elements enclosed in [&hellip;], each typed by these rules)
163
191
  - Hash, ([hash-key] followed by sub-elements, each typed by these rules) and,
164
192
  - Date and Time, when given in ISO form YYYY-MM-DD or YYYY-MM-DDThh:mm:ss
165
193
 
166
194
 
167
- <a id="org22479cd"></a>
195
+ <a id="orgdc9ffb1"></a>
168
196
 
169
197
  ### JSON
170
198
 
@@ -175,12 +203,12 @@ The `:json` style deserializes the following types:
175
203
  - Integer (when it looks like an decimal whole number, but NO provision hex or octal)
176
204
  - Float (when it looks like an decimal number)
177
205
  - String (only if enclosed in single- or double-quotes)
178
- - Array (when sub-elements enclosed in [&#x2026;], each typed by these rules)
179
- - Hash, (when sub-elements enclosed in {&#x2026;}, each typed by these rules) and,
206
+ - Array (when sub-elements enclosed in [&hellip;], each typed by these rules)
207
+ - Hash, (when sub-elements enclosed in {&hellip;}, each typed by these rules) and,
180
208
  - Date and Time, NOT deserialized, returns a parse error
181
209
 
182
210
 
183
- <a id="org850bdf4"></a>
211
+ <a id="orgd9553d5"></a>
184
212
 
185
213
  ### INI
186
214
 
@@ -188,7 +216,7 @@ The `:ini` style deserializes the following types:
188
216
 
189
217
  - TrueClass (exactly the string 'true')
190
218
  - FalseClass (exactly the string 'false')
191
- - Integer (when it looks like an whole number or 0x&#x2026; or 0o&#x2026; hex or octal)
219
+ - Integer (when it looks like an whole number or 0x&hellip; or 0o&hellip; hex or octal)
192
220
  - Float (when it looks like an decimal number)
193
221
  - String (anything else)
194
222
  - Array NOT deserialized, returned as a String
@@ -196,7 +224,7 @@ The `:ini` style deserializes the following types:
196
224
  - Date and Time, NOT deserialized, returned as a String
197
225
 
198
226
 
199
- <a id="org570f7c5"></a>
227
+ <a id="orgfd67663"></a>
200
228
 
201
229
  ## Creating a Reader
202
230
 
@@ -205,15 +233,19 @@ When creating a `Reader`, the `#new` method takes a mandatory argument that spec
205
233
  - `style:` specify a style for the config files other than YAML, the choices are `yaml`, `toml`, `json`, and `ini`. This can be given either as a String or Symbol in upper or lower case.
206
234
  - `xdg:` either `true`, to follow the XDG standard for where to find config files, or `false`, to follow classic UNIX conventions.
207
235
  - `root_prefix:`, to locate the root of the file system somewhere other than `/`. This is probably only useful in testing `FatConfig`.
236
+ - `user_dir:`, to explicitly set the name of the user directory in which to look for the app's config files, thus ignoring the setting of `xdg:` for the user-level settings.
237
+ - `sys_dir:`, to explicitly set the name of the system directory in which to look for the app's config files, thus ignoring the setting of `xdg:` for the system-level settings.
208
238
 
209
239
  ```ruby
210
240
  reader1 = FatConfig.new('labrat') # Use XDG and YAML
211
241
  reader2 = FatConfig.new('labrat', style: 'toml') # Use XDG and TOML
212
242
  reader3 = FatConfig.new('labrat', style: 'ini', xdg: false) # Use classic UNIX and INI style
243
+ reader4 = FatConfig.new('labrat', user_dir: '~/labrat') # Look in ~/labrat for user settings
244
+ reader5 = FatConfig.new('labrat', sys_dir: '/etc/labrat') # Look in /etc/labrat for system settings
213
245
  ```
214
246
 
215
247
 
216
- <a id="org9637e7b"></a>
248
+ <a id="orgd2e0f2e"></a>
217
249
 
218
250
  ## Calling the `read` method on a `Reader`
219
251
 
@@ -276,7 +308,7 @@ Here are the parsing rules:
276
308
  4. These rules apply regardless of style being used for config files.
277
309
 
278
310
 
279
- <a id="orgda3ea75"></a>
311
+ <a id="org247b85b"></a>
280
312
 
281
313
  # Development
282
314
 
@@ -285,14 +317,14 @@ After checking out the repo, run \`bin/setup\` to install dependencies. Then, ru
285
317
  To install this gem onto your local machine, run \`bundle exec rake install\`. To release a new version, update the version number in \`version.rb\`, and then run \`bundle exec rake release\`, which will create a git tag for the version, push git commits and the created tag, and push the \`.gem\` file to [rubygems.org](https://rubygems.org).
286
318
 
287
319
 
288
- <a id="orgfb56ba2"></a>
320
+ <a id="org52f251b"></a>
289
321
 
290
322
  # Contributing
291
323
 
292
324
  Bug reports and pull requests are welcome on GitHub at <https://github.com/ddoherty03/fat_config>.
293
325
 
294
326
 
295
- <a id="org0377369"></a>
327
+ <a id="orgd18d129"></a>
296
328
 
297
329
  # License
298
330
 
data/README.org CHANGED
@@ -5,6 +5,11 @@
5
5
  #+PROPERTY: header-args:sh :exports code :eval no
6
6
  #+PROPERTY: header-args:bash :exports code :eval no
7
7
 
8
+ # gem_docs:version begin
9
+ Version: 0.8.1
10
+ # gem_docs:version end
11
+
12
+
8
13
  [[https://github.com/ddoherty03/fat_config/actions/workflows/main.yml][https://github.com/ddoherty03/fat_config/actions/workflows/main.yml/badge.svg?branch=master]]
9
14
 
10
15
  * Introduction
@@ -30,6 +35,8 @@ the files in the proper priority. It can be set to read ~YAML~, ~TOML~,
30
35
  - [[#usage][Usage:]]
31
36
  - [[#following-xdg-standards][Following XDG Standards]]
32
37
  - [[#following-classic-unix-standards][Following Classic UNIX Standards]]
38
+ - [[#explicitly-providing-config-directories][Explicitly Providing Config Directories]]
39
+ - [[#windows-paths][Windows Paths]]
33
40
  - [[#available-config-file-styles][Available Config File Styles]]
34
41
  - [[#hash-keys][Hash Keys]]
35
42
  - [[#hash-values][Hash Values]]
@@ -84,7 +91,8 @@ from the following locations, from lowest priority to highest:
84
91
  file, it will look in that file for any system-level config file.
85
92
  2. If the environment variable ~MYAPP_SYS_CONFIG~ is NOT set, it will read any
86
93
  system-level config file from ~/etc/xdg/myapp~ or, if the ~XDG_CONFIG_DIRS~
87
- environment variable is set to a list of colon-separated directories, it
94
+ environment variable is set to a list of directories separated by ~File::PATH_SEPARATOR~ (colon on Unix,
95
+ semicolon on Windows), it
88
96
  will look in each of those instead of ~/etc/xdg~ for config directories
89
97
  called ~myapp~. If more than one ~XDG_CONFIG_DIRS~ is given, they are
90
98
  treated as listed in order of precedence, so the first-listed directory
@@ -164,12 +172,42 @@ hypothetical application called ~myapp~:
164
172
  ~Hash~, it is used directly and merged into the hash returned from the
165
173
  prior methods.
166
174
 
175
+ ** Explicitly Providing Config Directories
176
+ When the =Reader= is created, the user can give an explicit =user_dir:=,
177
+ =sys_dir:= parameter or both. If given, the XDG or classic conventions for
178
+ locating config files are ignored regardless of the setting of the =xdg:=
179
+ parameter.
180
+ ** Windows Paths
181
+ Native Windows Ruby accepts drive-qualified filenames and backslashes for
182
+ ~APPNAME_CONFIG~ and ~APPNAME_SYS_CONFIG~, as well as explicit ~user_dir:~ and
183
+ ~sys_dir:~ paths. Paths containing spaces need no extra quoting inside the
184
+ Ruby string or environment variable value. UNC filenames retain their server
185
+ and share prefix.
186
+
187
+ On Windows, separate ~XDG_CONFIG_DIRS~ entries with semicolons, for example
188
+ ~C:/Company/config;D:/Shared/config~. The first directory has highest priority.
189
+ Unix continues to use colons. ~XDG_CONFIG_HOME~ is always a single directory.
190
+
191
+ Default locations remain ~/etc~, ~/etc/xdg~, and =~/.config=; FatConfig does
192
+ not automatically select AppData or ProgramData. Set explicit directories or
193
+ environment variables when those locations are desired.
194
+
195
+ An empty or nil ~root_prefix:~ leaves expanded filenames intact. A nonempty
196
+ prefix is intended for test sandboxes: on Windows, ~C:/Users/me/config.yml~
197
+ maps to ~<root_prefix>/Users/me/config.yml~. Drive letters are removed only
198
+ when applying a sandbox prefix, so different drives share the sandbox root.
199
+ Explicit ~user_dir:~ and ~sys_dir:~ continue to bypass ~root_prefix:~.
200
+
201
+ *TOML is currently unsupported on native Windows.* The ~tomlib 0.7.3~
202
+ dependency can crash the Ruby process on malformed TOML, rather than raising
203
+ a parse error. Use YAML, JSON, or INI on Windows until the dependency is fixed.
204
+
167
205
  ** Available Config File Styles
168
206
  ~FatConfig::Reader.new~ takes the optional keyword argument, ~:style~, to
169
207
  indicate what style to use for config files. It can be one of:
170
208
 
171
209
  - ~yaml~ :: See [[https://yaml.org/spec/1.2.2/][YAML Specs]],
172
- - ~toml~ :: See [[https://toml.io/en/][TOML Specs]],
210
+ - ~toml~ :: See [[https://toml.io/en/][TOML Specs]] (currently unsupported on native Windows),
173
211
  - ~json~ :: See [[https://datatracker.ietf.org/doc/html/rfc8259][JSON Specs]], or
174
212
  - ~ini~ :: See [[https://en.wikipedia.org/wiki/INI_file][INI File on Wikipedia]]
175
213
 
@@ -253,11 +291,19 @@ sought. It also takes a few optional keyword arguments:
253
291
  files, or ~false~, to follow classic UNIX conventions.
254
292
  - ~root_prefix:~, to locate the root of the file system somewhere other than
255
293
  ~/~. This is probably only useful in testing ~FatConfig~.
294
+ - =user_dir:=, to explicitly set the name of the user directory in which to
295
+ look for the app's config files, thus ignoring the setting of =xdg:= for the
296
+ user-level settings.
297
+ - =sys_dir:=, to explicitly set the name of the system directory in which to
298
+ look for the app's config files, thus ignoring the setting of =xdg:= for the
299
+ system-level settings.
256
300
 
257
301
  #+begin_src ruby :eval no
258
302
  reader1 = FatConfig.new('labrat') # Use XDG and YAML
259
303
  reader2 = FatConfig.new('labrat', style: 'toml') # Use XDG and TOML
260
304
  reader3 = FatConfig.new('labrat', style: 'ini', xdg: false) # Use classic UNIX and INI style
305
+ reader4 = FatConfig.new('labrat', user_dir: '~/labrat') # Look in ~/labrat for user settings
306
+ reader5 = FatConfig.new('labrat', sys_dir: '/etc/labrat') # Look in /etc/labrat for system settings
261
307
  #+end_src
262
308
 
263
309
  ** Calling the ~read~ method on a ~Reader~
@@ -12,8 +12,9 @@ module FatConfig
12
12
  # - ~xdg~ :: whether follow XDG desktop conventions, by default true; if
13
13
  # false, use "classic" UNIX config practices with /etc/ and ~/.baserc.
14
14
  # - ~root_prefix~ :: an alternate root of the assumed file system, by
15
- # default ''. This facilitated testing.
15
+ # default ''. This facilitates testing.
16
16
  attr_reader :app_name, :style, :root_prefix, :xdg
17
+ attr_reader :user_dir, :sys_dir
17
18
 
18
19
  # Config file may be located in either the xdg locations (containing any
19
20
  # variant of base: base, base.yml, or base.yaml) or in the classic
@@ -38,7 +39,7 @@ module FatConfig
38
39
  # b. Then, either:
39
40
  # A. The file pointed to by the environment variable APPNAME_SYS_CONFIG or
40
41
  # B. System classic config files,
41
- def initialize(app_name, style: :yaml, xdg: true, root_prefix: '')
42
+ def initialize(app_name, style: :yaml, xdg: true, root_prefix: '', user_dir: nil, sys_dir: nil)
42
43
  @app_name = app_name.strip.downcase
43
44
  raise ArgumentError, "reader app name may not be blank" if @app_name.blank?
44
45
 
@@ -63,6 +64,10 @@ module FatConfig
63
64
  msg = "config style must be one of #{VALID_CONFIG_STYLES.join(', ')}"
64
65
  raise ArgumentError, msg
65
66
  end
67
+ # Optional override of directories instead of locating them by XDG or
68
+ # classical conventions.
69
+ @user_dir = user_dir
70
+ @sys_dir = sys_dir
66
71
  end
67
72
 
68
73
  # Return a Hash of the config files for app_name directories. For
@@ -102,6 +107,60 @@ module FatConfig
102
107
  merge_command_line(result, command_line, verbose: verbose)
103
108
  end
104
109
 
110
+ # Return a Hash of Arrays with the outer Hash having the key system:
111
+ # pointing to an Array of system paths and the key :user pointing to an
112
+ # Array of user paths.
113
+ #
114
+ # @param base [String] base name to use for finding config paths
115
+ # @param user_dir [String] optional override of directory with user config files
116
+ # @param sys_dir [String] optional override of directory with system config files
117
+ def config_paths(base = app_name)
118
+ sys_configs = []
119
+ sys_env_name = "#{app_name.upcase}_SYS_CONFIG"
120
+ if sys_dir
121
+ sys_configs = find_config_files_in_dir(base, sys_dir)
122
+ elsif ENV[sys_env_name]
123
+ sys_fname = prefixed_path(File.expand_path(ENV[sys_env_name]))
124
+ sys_configs << sys_fname if File.readable?(sys_fname)
125
+ else
126
+ sys_configs +=
127
+ if xdg
128
+ find_xdg_sys_config_files(base)
129
+ else
130
+ find_classic_sys_config_files(base)
131
+ end
132
+ end
133
+
134
+ usr_configs = []
135
+ usr_env_name = "#{app_name.upcase}_CONFIG"
136
+ if user_dir
137
+ usr_configs = find_config_files_in_dir(base, user_dir)
138
+ elsif ENV[usr_env_name]
139
+ usr_fname = prefixed_path(File.expand_path(ENV[usr_env_name]))
140
+ usr_configs << usr_fname if File.readable?(usr_fname)
141
+ else
142
+ usr_configs <<
143
+ if xdg
144
+ find_xdg_user_config_file(base)
145
+ else
146
+ find_classic_user_config_file(base)
147
+ end
148
+ end
149
+ { system: sys_configs.compact, user: usr_configs.compact }
150
+ end
151
+
152
+ private
153
+
154
+ # Leave native absolute paths intact unless a sandbox root was requested.
155
+ # A Windows drive cannot appear inside the sandbox path; map its root to
156
+ # root_prefix, just as the Unix filesystem root is mapped there.
157
+ def prefixed_path(path)
158
+ return path if root_prefix.to_s.strip.empty?
159
+
160
+ path = path.sub(/\A[A-Za-z]:/, '') if File::ALT_SEPARATOR
161
+ File.join(root_prefix, path)
162
+ end
163
+
105
164
  def merge_environment(start_hash, verbose: false)
106
165
  return start_hash if ENV[env_name].blank?
107
166
 
@@ -138,35 +197,25 @@ module FatConfig
138
197
  "#{app_name.upcase}_OPTIONS"
139
198
  end
140
199
 
141
- def config_paths(base = app_name)
142
- sys_configs = []
143
- sys_env_name = "#{app_name.upcase}_SYS_CONFIG"
144
- if ENV[sys_env_name]
145
- sys_fname = File.join(root_prefix, File.expand_path(ENV[sys_env_name]))
146
- sys_configs << sys_fname if File.readable?(sys_fname)
147
- else
148
- sys_configs +=
149
- if xdg
150
- find_xdg_sys_config_files(base)
151
- else
152
- find_classic_sys_config_files(base)
153
- end
154
- end
200
+ ########################################################################
201
+ # Explicit config files
202
+ ########################################################################
155
203
 
156
- usr_configs = []
157
- usr_env_name = "#{app_name.upcase}_CONFIG"
158
- if ENV[usr_env_name]
159
- usr_fname = File.join(root_prefix, File.expand_path(ENV[usr_env_name]))
160
- usr_configs << usr_fname if File.readable?(usr_fname)
161
- else
162
- usr_configs <<
163
- if xdg
164
- find_xdg_user_config_file(base)
165
- else
166
- find_classic_user_config_file(base)
167
- end
204
+ # Return a list of readable config files with basename base for the
205
+ # current style in directory dir.
206
+ #
207
+ # @param base [String] base name of config file
208
+ # @param dir [String] directory to search for config giles
209
+ def find_config_files_in_dir(base, dir)
210
+ configs = []
211
+ dir = File.expand_path(dir)
212
+ base_candidates = style.dir_constrained_base_names(base)
213
+ base_candidates.each do |f|
214
+ if File.readable?(File.join(dir, f))
215
+ configs << File.join(dir, f)
216
+ end
168
217
  end
169
- { system: sys_configs.compact, user: usr_configs.compact }
218
+ configs
170
219
  end
171
220
 
172
221
  ########################################################################
@@ -181,15 +230,15 @@ module FatConfig
181
230
  # $XDG_CONFIG_DIRS (default: "/etc/xdg"): precedence-ordered set of system configuration directories.
182
231
 
183
232
  # Return the absolute path names of all XDG system config files for
184
- # app_name with the basename variants of base. Return the lowest priority
185
- # files first, highest last. Prefix the search locations with dir_prefix
186
- # if given.
233
+ # app_name in the current style with the basename variants of base. Return
234
+ # the lowest priority files first, highest last. Prefix the search
235
+ # locations with dir_prefix if given.
187
236
  def find_xdg_sys_config_files(base = app_name)
188
237
  configs = []
189
- xdg_search_dirs = ENV['XDG_CONFIG_DIRS']&.split(':')&.reverse || ['/etc/xdg']
238
+ xdg_search_dirs = ENV['XDG_CONFIG_DIRS']&.split(File::PATH_SEPARATOR)&.reverse || ['/etc/xdg']
190
239
  xdg_search_dirs.each do |dir|
191
240
  dir = File.expand_path(File.join(dir, app_name))
192
- dir = File.join(root_prefix, dir) unless root_prefix.nil? || root_prefix.strip.empty?
241
+ dir = prefixed_path(dir)
193
242
  base_candidates = style.dir_constrained_base_names(base)
194
243
  config_fname = base_candidates.find { |b| File.readable?(File.join(dir, b)) }
195
244
  configs << File.join(dir, config_fname) if config_fname
@@ -198,16 +247,16 @@ module FatConfig
198
247
  end
199
248
 
200
249
  # Return the absolute path name of any XDG user config files for app_name
201
- # with the basename variants of base. The XDG_CONFIG_HOME environment
202
- # variable for the user configs is intended to be the name of a single xdg
203
- # config directory, not a list of colon-separated directories as for the
204
- # system config. Return the name of a config file for this app in
205
- # XDG_CONFIG_HOME (or ~/.config by default). Prefix the search location
206
- # with dir_prefix if given.
250
+ # in the current style with the basename variants of base. The
251
+ # XDG_CONFIG_HOME environment variable for the user configs is intended to
252
+ # be the name of a single xdg config directory, not a list of
253
+ # path-separated directories as for the system config. Return the name of
254
+ # a config file for this app in XDG_CONFIG_HOME (or ~/.config by default).
255
+ # Prefix the search location with dir_prefix if given.
207
256
  def find_xdg_user_config_file(base = app_name)
208
257
  xdg_search_dir = ENV['XDG_CONFIG_HOME'] || ['~/.config']
209
258
  dir = File.expand_path(File.join(xdg_search_dir, app_name))
210
- dir = File.join(root_prefix, dir) unless root_prefix.strip.empty?
259
+ dir = prefixed_path(dir)
211
260
  return unless Dir.exist?(dir)
212
261
 
213
262
  base_candidates = style.dir_constrained_base_names(base)
@@ -222,20 +271,20 @@ module FatConfig
222
271
  ########################################################################
223
272
 
224
273
  # Return the absolute path names of all "classic" system config files for
225
- # app_name with the basename variants of base. Return the lowest priority
226
- # files first, highest last. Prefix the search locations with dir_prefix
227
- # if given.
274
+ # app_name in the current style with the basename variants of base. Return
275
+ # the lowest priority files first, highest last. Prefix the search
276
+ # locations with dir_prefix if given.
228
277
  def find_classic_sys_config_files(base = app_name)
229
278
  configs = []
230
279
  env_config = ENV["#{app_name.upcase}_SYS_CONFIG"]
231
- if env_config && File.readable?(config = File.join(root_prefix, File.expand_path(env_config)))
280
+ if env_config && File.readable?(config = prefixed_path(File.expand_path(env_config)))
232
281
  configs = [config]
233
- elsif File.readable?(config = File.join(root_prefix, "/etc/#{base}"))
282
+ elsif File.readable?(config = prefixed_path("/etc/#{base}"))
234
283
  configs = [config]
235
- elsif File.readable?(config = File.join(root_prefix, "/etc/#{base}rc"))
284
+ elsif File.readable?(config = prefixed_path("/etc/#{base}rc"))
236
285
  configs = [config]
237
286
  else
238
- dir = File.join(root_prefix, "/etc/#{app_name}")
287
+ dir = prefixed_path("/etc/#{app_name}")
239
288
  if Dir.exist?(dir)
240
289
  base_candidates = style.classic_base_names(base)
241
290
  config = base_candidates.find { |b| File.readable?(File.join(dir, b)) }
@@ -246,22 +295,22 @@ module FatConfig
246
295
  end
247
296
 
248
297
  # Return the absolute path names of all "classic" system config files for
249
- # app_name with the basename variants of base. Return the lowest priority
250
- # files first, highest last. Prefix the search locations with dir_prefix if
251
- # given.
298
+ # app_name in the current style with the basename variants of base. Return
299
+ # the lowest priority files first, highest last. Prefix the search
300
+ # locations with dir_prefix if given.
252
301
  def find_classic_user_config_file(base = app_name)
253
302
  env_config = ENV["#{app_name.upcase}_CONFIG"]
254
- if env_config && File.readable?(config = File.join(root_prefix, File.expand_path(env_config)))
303
+ if env_config && File.readable?(config = prefixed_path(File.expand_path(env_config)))
255
304
  config
256
305
  else
257
- config_dir = File.join(root_prefix, File.expand_path("~/"))
306
+ config_dir = prefixed_path(File.expand_path("~/"))
258
307
  base_candidates = style.dotted_base_names(base)
259
308
  base_fname = base_candidates.find do |b|
260
309
  File.file?(File.join(config_dir, b)) && File.readable?(File.join(config_dir, b))
261
310
  end
262
311
  if base_fname
263
312
  File.join(config_dir, base_fname)
264
- elsif Dir.exist?(config_dir = File.join(root_prefix, File.expand_path("~/.#{app_name}")))
313
+ elsif Dir.exist?(config_dir = prefixed_path(File.expand_path("~/.#{app_name}")))
265
314
  base_candidates = style.dir_constrained_base_names(base)
266
315
  base_fname = base_candidates.find { |b| File.readable?(File.join(config_dir, b)) }
267
316
  File.join(config_dir, base_fname) if base_fname
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FatConfig
4
- VERSION = "0.7.1"
4
+ VERSION = "0.8.1"
5
5
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fat_config
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.1
4
+ version: 0.8.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel E. Doherty
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-02-06 00:00:00.000000000 Z
10
+ date: 2026-11-14 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: activesupport