json-database 0.8.0a1__py3-none-any.whl
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.
- json_database/__init__.py +387 -0
- json_database/crypto.py +80 -0
- json_database/exceptions.py +22 -0
- json_database/search.py +169 -0
- json_database/utils.py +340 -0
- json_database/version.py +6 -0
- json_database/xdg_utils.py +174 -0
- json_database-0.8.0a1.dist-info/LICENSE +19 -0
- json_database-0.8.0a1.dist-info/METADATA +205 -0
- json_database-0.8.0a1.dist-info/RECORD +12 -0
- json_database-0.8.0a1.dist-info/WHEEL +5 -0
- json_database-0.8.0a1.dist-info/top_level.txt +1 -0
json_database/utils.py
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from difflib import SequenceMatcher
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class DummyLock:
|
|
6
|
+
""" A fake lock.
|
|
7
|
+
|
|
8
|
+
Arguments:
|
|
9
|
+
path (str): path to the lockfile for the lock
|
|
10
|
+
"""
|
|
11
|
+
def __init__(self, path=""):
|
|
12
|
+
self.path = path
|
|
13
|
+
|
|
14
|
+
def acquire(self, blocking=True):
|
|
15
|
+
""" Acquire lock, locks thread and process lock.
|
|
16
|
+
|
|
17
|
+
Arguments:
|
|
18
|
+
blocking(bool): Set's blocking mode of acquire operation.
|
|
19
|
+
Default True.
|
|
20
|
+
|
|
21
|
+
Returns: True if lock succeeded otherwise False
|
|
22
|
+
"""
|
|
23
|
+
return True
|
|
24
|
+
|
|
25
|
+
def release(self):
|
|
26
|
+
""" Release acquired lock. """
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
def __enter__(self):
|
|
30
|
+
""" Context handler, acquires lock in blocking mode. """
|
|
31
|
+
return self
|
|
32
|
+
|
|
33
|
+
def __exit__(self, _type, value, traceback):
|
|
34
|
+
""" Releases the lock. """
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def fuzzy_match(x, against):
|
|
39
|
+
"""Perform a 'fuzzy' comparison between two strings.
|
|
40
|
+
Returns:
|
|
41
|
+
float: match percentage -- 1.0 for perfect match,
|
|
42
|
+
down to 0.0 for no match at all.
|
|
43
|
+
"""
|
|
44
|
+
return SequenceMatcher(None, x, against).ratio()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def match_one(query, choices):
|
|
48
|
+
"""
|
|
49
|
+
Find best match from a list or dictionary given an input
|
|
50
|
+
|
|
51
|
+
Arguments:
|
|
52
|
+
query: string to test
|
|
53
|
+
choices: list or dictionary of choices
|
|
54
|
+
|
|
55
|
+
Returns: tuple with best match, score
|
|
56
|
+
"""
|
|
57
|
+
if isinstance(choices, dict):
|
|
58
|
+
_choices = list(choices.keys())
|
|
59
|
+
elif isinstance(choices, list):
|
|
60
|
+
_choices = choices
|
|
61
|
+
else:
|
|
62
|
+
raise ValueError('a list or dict of choices must be provided')
|
|
63
|
+
|
|
64
|
+
best = (_choices[0], fuzzy_match(query, _choices[0]))
|
|
65
|
+
for c in _choices[1:]:
|
|
66
|
+
score = fuzzy_match(query, c)
|
|
67
|
+
if score > best[1]:
|
|
68
|
+
best = (c, score)
|
|
69
|
+
|
|
70
|
+
if isinstance(choices, dict):
|
|
71
|
+
return (choices[best[0]], best[1])
|
|
72
|
+
else:
|
|
73
|
+
return best
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def merge_dict(base, delta, merge_lists=True, skip_empty=True,
|
|
77
|
+
no_dupes=True, new_only=False):
|
|
78
|
+
"""
|
|
79
|
+
Recursively merging configuration dictionaries.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
base: Target for merge
|
|
83
|
+
delta: Dictionary to merge into base
|
|
84
|
+
merge_lists: if a list is found merge contents instead of replacing
|
|
85
|
+
skip_empty: if an item in delta is empty, dont overwrite base
|
|
86
|
+
no_dupes: when merging lists deduplicate entries
|
|
87
|
+
new_only: only merge keys not yet in base
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
for k, d in delta.items():
|
|
91
|
+
b = base.get(k)
|
|
92
|
+
if isinstance(d, dict) and isinstance(b, dict):
|
|
93
|
+
merge_dict(b, d, merge_lists, skip_empty, no_dupes, new_only)
|
|
94
|
+
else:
|
|
95
|
+
if new_only and k in base:
|
|
96
|
+
continue
|
|
97
|
+
if skip_empty and not d and d is not False:
|
|
98
|
+
# dont replace if new entry is empty
|
|
99
|
+
pass
|
|
100
|
+
elif all((isinstance(b, list), isinstance(d, list), merge_lists)):
|
|
101
|
+
if no_dupes:
|
|
102
|
+
base[k] += [item for item in d if item not in base[k]]
|
|
103
|
+
else:
|
|
104
|
+
base[k] += d
|
|
105
|
+
else:
|
|
106
|
+
base[k] = d
|
|
107
|
+
return base
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def load_commented_json(filename):
|
|
111
|
+
""" Loads an JSON file, ignoring comments
|
|
112
|
+
|
|
113
|
+
Supports a trivial extension to the JSON file format. Allow comments
|
|
114
|
+
to be embedded within the JSON, requiring that a comment be on an
|
|
115
|
+
independent line starting with '//' or '#'.
|
|
116
|
+
|
|
117
|
+
NOTE: A file created with these style comments will break strict JSON
|
|
118
|
+
parsers. This is similar to but lighter-weight than "human json"
|
|
119
|
+
proposed at https://hjson.org
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
filename (str): path to the commented JSON file
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
obj: decoded Python object
|
|
126
|
+
"""
|
|
127
|
+
with open(filename, encoding='utf-8') as f:
|
|
128
|
+
contents = f.read()
|
|
129
|
+
|
|
130
|
+
return json.loads(uncomment_json(contents))
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def uncomment_json(commented_json_str):
|
|
134
|
+
""" Removes comments from a JSON string.
|
|
135
|
+
|
|
136
|
+
Supporting a trivial extension to the JSON format. Allow comments
|
|
137
|
+
to be embedded within the JSON, requiring that a comment be on an
|
|
138
|
+
independent line starting with '//' or '#'.
|
|
139
|
+
|
|
140
|
+
Example...
|
|
141
|
+
{
|
|
142
|
+
// comment
|
|
143
|
+
'name' : 'value'
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
commented_json_str (str): a JSON string
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
str: uncommented, legal JSON
|
|
151
|
+
"""
|
|
152
|
+
lines = commented_json_str.splitlines()
|
|
153
|
+
# remove all comment lines, starting with // or #
|
|
154
|
+
nocomment = []
|
|
155
|
+
for line in lines:
|
|
156
|
+
stripped = line.lstrip()
|
|
157
|
+
if stripped.startswith("//") or stripped.startswith("#"):
|
|
158
|
+
continue
|
|
159
|
+
nocomment.append(line)
|
|
160
|
+
|
|
161
|
+
return " ".join(nocomment)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def is_jsonifiable(thing):
|
|
165
|
+
if not isinstance(thing, dict):
|
|
166
|
+
if isinstance(thing, str):
|
|
167
|
+
try:
|
|
168
|
+
json.loads(thing)
|
|
169
|
+
return True
|
|
170
|
+
except:
|
|
171
|
+
pass
|
|
172
|
+
else:
|
|
173
|
+
try:
|
|
174
|
+
thing.__dict__
|
|
175
|
+
return True
|
|
176
|
+
except:
|
|
177
|
+
pass
|
|
178
|
+
return False
|
|
179
|
+
return True
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def get_key_recursively(search_dict, field, filter_None=True):
|
|
183
|
+
"""
|
|
184
|
+
Takes a dict with nested lists and dicts,
|
|
185
|
+
and searches all dicts for a key of the field
|
|
186
|
+
provided.
|
|
187
|
+
"""
|
|
188
|
+
if not is_jsonifiable(search_dict):
|
|
189
|
+
raise ValueError("unparseable format")
|
|
190
|
+
fields_found = []
|
|
191
|
+
|
|
192
|
+
for key, value in search_dict.items():
|
|
193
|
+
if value is None and filter_None:
|
|
194
|
+
continue
|
|
195
|
+
if key == field:
|
|
196
|
+
fields_found.append(search_dict)
|
|
197
|
+
|
|
198
|
+
elif isinstance(value, dict):
|
|
199
|
+
fields_found += get_key_recursively(value, field, filter_None)
|
|
200
|
+
|
|
201
|
+
elif isinstance(value, list):
|
|
202
|
+
for item in value:
|
|
203
|
+
if not isinstance(item, dict):
|
|
204
|
+
try:
|
|
205
|
+
if get_key_recursively(item.__dict__, field, filter_None):
|
|
206
|
+
fields_found.append(item)
|
|
207
|
+
except:
|
|
208
|
+
continue # can't parse
|
|
209
|
+
else:
|
|
210
|
+
fields_found += get_key_recursively(item, field, filter_None)
|
|
211
|
+
|
|
212
|
+
return fields_found
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def get_key_recursively_fuzzy(search_dict, field, thresh=0.6, filter_None=True):
|
|
216
|
+
"""
|
|
217
|
+
Takes a dict with nested lists and dicts,
|
|
218
|
+
and searches all dicts for a key of the field
|
|
219
|
+
provided.
|
|
220
|
+
"""
|
|
221
|
+
if not is_jsonifiable(search_dict):
|
|
222
|
+
raise ValueError("unparseable format")
|
|
223
|
+
|
|
224
|
+
fields_found = []
|
|
225
|
+
|
|
226
|
+
for key, value in search_dict.items():
|
|
227
|
+
if value is None and filter_None:
|
|
228
|
+
continue
|
|
229
|
+
score = 0
|
|
230
|
+
if isinstance(key, str):
|
|
231
|
+
score = fuzzy_match(key, field)
|
|
232
|
+
|
|
233
|
+
if score >= thresh:
|
|
234
|
+
fields_found.append((search_dict, score))
|
|
235
|
+
elif isinstance(value, dict):
|
|
236
|
+
fields_found += get_key_recursively_fuzzy(value, field, thresh, filter_None)
|
|
237
|
+
|
|
238
|
+
elif isinstance(value, list):
|
|
239
|
+
for item in value:
|
|
240
|
+
if not isinstance(item, dict):
|
|
241
|
+
try:
|
|
242
|
+
if get_key_recursively_fuzzy(item.__dict__, field, thresh, filter_None):
|
|
243
|
+
fields_found.append((item, score))
|
|
244
|
+
except:
|
|
245
|
+
continue # can't parse
|
|
246
|
+
else:
|
|
247
|
+
fields_found += get_key_recursively_fuzzy(item, field, thresh, filter_None)
|
|
248
|
+
return sorted(fields_found, key = lambda i: i[1],reverse=True)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def get_value_recursively(search_dict, field, target_value):
|
|
252
|
+
"""
|
|
253
|
+
Takes a dict with nested lists and dicts,
|
|
254
|
+
and searches all dicts for a key of the field
|
|
255
|
+
provided.
|
|
256
|
+
"""
|
|
257
|
+
if not is_jsonifiable(search_dict):
|
|
258
|
+
raise ValueError("unparseable format")
|
|
259
|
+
fields_found = []
|
|
260
|
+
|
|
261
|
+
for key, value in search_dict.items():
|
|
262
|
+
|
|
263
|
+
if key == field and value == target_value:
|
|
264
|
+
fields_found.append(search_dict)
|
|
265
|
+
|
|
266
|
+
elif isinstance(value, dict):
|
|
267
|
+
fields_found += get_value_recursively(value, field, target_value)
|
|
268
|
+
|
|
269
|
+
elif isinstance(value, list):
|
|
270
|
+
for item in value:
|
|
271
|
+
if not isinstance(item, dict):
|
|
272
|
+
try:
|
|
273
|
+
if get_value_recursively(item.__dict__, field, target_value):
|
|
274
|
+
fields_found.append(item)
|
|
275
|
+
except:
|
|
276
|
+
continue # can't parse
|
|
277
|
+
else:
|
|
278
|
+
fields_found += get_value_recursively(item, field, target_value)
|
|
279
|
+
|
|
280
|
+
return fields_found
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def get_value_recursively_fuzzy(search_dict, field, target_value, thresh=0.6):
|
|
284
|
+
"""
|
|
285
|
+
Takes a dict with nested lists and dicts,
|
|
286
|
+
and searches all dicts for a key of the field
|
|
287
|
+
provided.
|
|
288
|
+
"""
|
|
289
|
+
if not is_jsonifiable(search_dict):
|
|
290
|
+
raise ValueError("unparseable format")
|
|
291
|
+
fields_found = []
|
|
292
|
+
for key, value in search_dict.items():
|
|
293
|
+
if key == field:
|
|
294
|
+
if isinstance(value, str):
|
|
295
|
+
score = fuzzy_match(target_value, value)
|
|
296
|
+
if score >= thresh:
|
|
297
|
+
fields_found.append((search_dict, score))
|
|
298
|
+
elif isinstance(value, list):
|
|
299
|
+
for item in value:
|
|
300
|
+
score = fuzzy_match(target_value, item)
|
|
301
|
+
if score >= thresh:
|
|
302
|
+
fields_found.append((search_dict, score))
|
|
303
|
+
elif isinstance(value, dict):
|
|
304
|
+
fields_found += get_value_recursively_fuzzy(value, field, target_value, thresh)
|
|
305
|
+
|
|
306
|
+
elif isinstance(value, list):
|
|
307
|
+
for item in value:
|
|
308
|
+
if not isinstance(item, dict):
|
|
309
|
+
try:
|
|
310
|
+
found = get_value_recursively_fuzzy(item.__dict__, field, target_value, thresh)
|
|
311
|
+
if len(found):
|
|
312
|
+
fields_found.append((item, found[0][1]))
|
|
313
|
+
except:
|
|
314
|
+
continue # can't parse
|
|
315
|
+
else:
|
|
316
|
+
fields_found += get_value_recursively_fuzzy(item, field, target_value, thresh)
|
|
317
|
+
|
|
318
|
+
return sorted(fields_found, key = lambda i: i[1],reverse=True)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def jsonify_recursively(thing):
|
|
322
|
+
if isinstance(thing, list):
|
|
323
|
+
jsonified = list(thing)
|
|
324
|
+
for idx, item in enumerate(thing):
|
|
325
|
+
jsonified[idx] = jsonify_recursively(item)
|
|
326
|
+
elif isinstance(thing, dict):
|
|
327
|
+
try:
|
|
328
|
+
# can't import at top level to do proper check
|
|
329
|
+
jsonified = dict(thing.db)
|
|
330
|
+
except:
|
|
331
|
+
jsonified = dict(thing)
|
|
332
|
+
for key in jsonified.keys():
|
|
333
|
+
value = jsonified[key]
|
|
334
|
+
jsonified[key] = jsonify_recursively(value)
|
|
335
|
+
else:
|
|
336
|
+
try:
|
|
337
|
+
jsonified = thing.__dict__
|
|
338
|
+
except:
|
|
339
|
+
jsonified = thing
|
|
340
|
+
return jsonified
|
json_database/version.py
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# Copyright © 2016-2021 Scott Stevenson <scott@stevenson.io>
|
|
2
|
+
#
|
|
3
|
+
# Permission to use, copy, modify, and/or distribute this software for
|
|
4
|
+
# any purpose with or without fee is hereby granted, provided that the
|
|
5
|
+
# above copyright notice and this permission notice appear in all
|
|
6
|
+
# copies.
|
|
7
|
+
#
|
|
8
|
+
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
|
9
|
+
# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
|
10
|
+
# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
|
11
|
+
# AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
|
|
12
|
+
# DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
|
|
13
|
+
# PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
|
14
|
+
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
15
|
+
# PERFORMANCE OF THIS SOFTWARE.
|
|
16
|
+
|
|
17
|
+
"""XDG Base Directory Specification variables.
|
|
18
|
+
|
|
19
|
+
xdg_cache_home(), xdg_config_home(), xdg_data_home(), and xdg_state_home()
|
|
20
|
+
return pathlib.Path objects containing the value of the environment variable
|
|
21
|
+
named XDG_CACHE_HOME, XDG_CONFIG_HOME, XDG_DATA_HOME, and XDG_STATE_HOME
|
|
22
|
+
respectively, or the default defined in the specification if the environment
|
|
23
|
+
variable is unset, empty, or contains a relative path rather than absolute
|
|
24
|
+
path.
|
|
25
|
+
|
|
26
|
+
xdg_config_dirs() and xdg_data_dirs() return a list of pathlib.Path
|
|
27
|
+
objects containing the value, split on colons, of the environment
|
|
28
|
+
variable named XDG_CONFIG_DIRS and XDG_DATA_DIRS respectively, or the
|
|
29
|
+
default defined in the specification if the environment variable is
|
|
30
|
+
unset or empty. Relative paths are ignored, as per the specification.
|
|
31
|
+
|
|
32
|
+
xdg_runtime_dir() returns a pathlib.Path object containing the value of
|
|
33
|
+
the XDG_RUNTIME_DIR environment variable, or None if the environment
|
|
34
|
+
variable is not set, or contains a relative path rather than absolute path.
|
|
35
|
+
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
# pylint: disable=fixme
|
|
39
|
+
|
|
40
|
+
import os
|
|
41
|
+
from pathlib import Path
|
|
42
|
+
from typing import List, Optional
|
|
43
|
+
|
|
44
|
+
__all__ = [
|
|
45
|
+
"xdg_cache_home",
|
|
46
|
+
"xdg_config_dirs",
|
|
47
|
+
"xdg_config_home",
|
|
48
|
+
"xdg_data_dirs",
|
|
49
|
+
"xdg_data_home",
|
|
50
|
+
"xdg_runtime_dir",
|
|
51
|
+
"xdg_state_home",
|
|
52
|
+
"XDG_CACHE_HOME",
|
|
53
|
+
"XDG_CONFIG_DIRS",
|
|
54
|
+
"XDG_CONFIG_HOME",
|
|
55
|
+
"XDG_DATA_DIRS",
|
|
56
|
+
"XDG_DATA_HOME",
|
|
57
|
+
"XDG_RUNTIME_DIR",
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _path_from_env(variable: str, default: Path) -> Path:
|
|
62
|
+
"""Read an environment variable as a path.
|
|
63
|
+
|
|
64
|
+
The environment variable with the specified name is read, and its
|
|
65
|
+
value returned as a path. If the environment variable is not set, is
|
|
66
|
+
set to the empty string, or is set to a relative rather than
|
|
67
|
+
absolute path, the default value is returned.
|
|
68
|
+
|
|
69
|
+
Parameters
|
|
70
|
+
----------
|
|
71
|
+
variable : str
|
|
72
|
+
Name of the environment variable.
|
|
73
|
+
default : Path
|
|
74
|
+
Default value.
|
|
75
|
+
|
|
76
|
+
Returns
|
|
77
|
+
-------
|
|
78
|
+
Path
|
|
79
|
+
Value from environment or default.
|
|
80
|
+
|
|
81
|
+
"""
|
|
82
|
+
# TODO(srstevenson): Use assignment expression in Python 3.8.
|
|
83
|
+
value = os.environ.get(variable)
|
|
84
|
+
if value and os.path.isabs(value):
|
|
85
|
+
return Path(value)
|
|
86
|
+
return default
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _paths_from_env(variable: str, default: List[Path]) -> List[Path]:
|
|
90
|
+
"""Read an environment variable as a list of paths.
|
|
91
|
+
|
|
92
|
+
The environment variable with the specified name is read, and its
|
|
93
|
+
value split on colons and returned as a list of paths. If the
|
|
94
|
+
environment variable is not set, or set to the empty string, the
|
|
95
|
+
default value is returned. Relative paths are ignored, as per the
|
|
96
|
+
specification.
|
|
97
|
+
|
|
98
|
+
Parameters
|
|
99
|
+
----------
|
|
100
|
+
variable : str
|
|
101
|
+
Name of the environment variable.
|
|
102
|
+
default : List[Path]
|
|
103
|
+
Default value.
|
|
104
|
+
|
|
105
|
+
Returns
|
|
106
|
+
-------
|
|
107
|
+
List[Path]
|
|
108
|
+
Value from environment or default.
|
|
109
|
+
|
|
110
|
+
"""
|
|
111
|
+
# TODO(srstevenson): Use assignment expression in Python 3.8.
|
|
112
|
+
value = os.environ.get(variable)
|
|
113
|
+
if value:
|
|
114
|
+
paths = [
|
|
115
|
+
Path(path) for path in value.split(":") if os.path.isabs(path)
|
|
116
|
+
]
|
|
117
|
+
if paths:
|
|
118
|
+
return paths
|
|
119
|
+
return default
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def xdg_cache_home() -> Path:
|
|
123
|
+
"""Return a Path corresponding to XDG_CACHE_HOME."""
|
|
124
|
+
return _path_from_env("XDG_CACHE_HOME", Path.home() / ".cache")
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def xdg_config_dirs() -> List[Path]:
|
|
128
|
+
"""Return a list of Paths corresponding to XDG_CONFIG_DIRS."""
|
|
129
|
+
return _paths_from_env("XDG_CONFIG_DIRS", [Path("/etc/xdg")])
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def xdg_config_home() -> Path:
|
|
133
|
+
"""Return a Path corresponding to XDG_CONFIG_HOME."""
|
|
134
|
+
return _path_from_env("XDG_CONFIG_HOME", Path.home() / ".config")
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def xdg_data_dirs() -> List[Path]:
|
|
138
|
+
"""Return a list of Paths corresponding to XDG_DATA_DIRS."""
|
|
139
|
+
return _paths_from_env(
|
|
140
|
+
"XDG_DATA_DIRS",
|
|
141
|
+
[Path(path) for path in "/usr/local/share/:/usr/share/".split(":")],
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def xdg_data_home() -> Path:
|
|
146
|
+
"""Return a Path corresponding to XDG_DATA_HOME."""
|
|
147
|
+
return _path_from_env("XDG_DATA_HOME", Path.home() / ".local" / "share")
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def xdg_runtime_dir() -> Optional[Path]:
|
|
151
|
+
"""Return a Path corresponding to XDG_RUNTIME_DIR.
|
|
152
|
+
|
|
153
|
+
If the XDG_RUNTIME_DIR environment variable is not set, None will be
|
|
154
|
+
returned as per the specification.
|
|
155
|
+
|
|
156
|
+
"""
|
|
157
|
+
value = os.getenv("XDG_RUNTIME_DIR")
|
|
158
|
+
if value and os.path.isabs(value):
|
|
159
|
+
return Path(value)
|
|
160
|
+
return None
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def xdg_state_home() -> Path:
|
|
164
|
+
"""Return a Path corresponding to XDG_STATE_HOME."""
|
|
165
|
+
return _path_from_env("XDG_STATE_HOME", Path.home() / ".local" / "state")
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
# The following variables are deprecated, but remain for backward compatibility.
|
|
169
|
+
XDG_CACHE_HOME = xdg_cache_home()
|
|
170
|
+
XDG_CONFIG_DIRS = xdg_config_dirs()
|
|
171
|
+
XDG_CONFIG_HOME = xdg_config_home()
|
|
172
|
+
XDG_DATA_DIRS = xdg_data_dirs()
|
|
173
|
+
XDG_DATA_HOME = xdg_data_home()
|
|
174
|
+
XDG_RUNTIME_DIR = xdg_runtime_dir()
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
The MIT License
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
|
11
|
+
all 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 FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
19
|
+
THE SOFTWARE.
|