file-groups 0.3.0__py3-none-any.whl → 0.3.1__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.
- file_groups/__init__.py +2 -0
- file_groups/config_files.py +107 -82
- file_groups/groups.py +43 -39
- file_groups/handler.py +1 -1
- file_groups/handler_compare.py +1 -1
- {file_groups-0.3.0.dist-info → file_groups-0.3.1.dist-info}/METADATA +7 -6
- file_groups-0.3.1.dist-info/RECORD +14 -0
- {file_groups-0.3.0.dist-info → file_groups-0.3.1.dist-info}/WHEEL +1 -1
- file_groups-0.3.0.dist-info/RECORD +0 -14
- {file_groups-0.3.0.dist-info → file_groups-0.3.1.dist-info}/LICENSE.txt +0 -0
- {file_groups-0.3.0.dist-info → file_groups-0.3.1.dist-info}/top_level.txt +0 -0
- {file_groups-0.3.0.dist-info → file_groups-0.3.1.dist-info}/zip-safe +0 -0
file_groups/__init__.py
CHANGED
file_groups/config_files.py
CHANGED
@@ -7,7 +7,7 @@ import itertools
|
|
7
7
|
from pprint import pformat
|
8
8
|
import logging
|
9
9
|
from dataclasses import dataclass
|
10
|
-
from typing import Tuple, Sequence,
|
10
|
+
from typing import Tuple, Sequence, Any
|
11
11
|
|
12
12
|
from appdirs import AppDirs # type: ignore
|
13
13
|
|
@@ -22,22 +22,47 @@ class ConfigException(Exception):
|
|
22
22
|
|
23
23
|
|
24
24
|
@dataclass
|
25
|
-
class
|
26
|
-
"""Hold
|
27
|
-
|
25
|
+
class ProtectConfig():
|
26
|
+
"""Hold global (site or user) protect config."""
|
27
|
+
protect_recursive: set[re.Pattern]
|
28
|
+
|
29
|
+
def __json__(self):
|
30
|
+
return {
|
31
|
+
ProtectConfig.__name__: {
|
32
|
+
"protect_recursive": [str(pat) for pat in self.protect_recursive],
|
33
|
+
}
|
34
|
+
}
|
35
|
+
|
36
|
+
|
37
|
+
@dataclass
|
38
|
+
class DirConfig(ProtectConfig):
|
39
|
+
"""Hold directory specific protect config."""
|
40
|
+
protect_local: set[re.Pattern]
|
28
41
|
config_dir: Path|None
|
29
|
-
config_files:
|
42
|
+
config_files: list[str]
|
30
43
|
|
31
44
|
def is_protected(self, ff: FsPath):
|
32
45
|
"""If ff id protected by a regex pattern then return the pattern, otherwise return None."""
|
33
46
|
|
34
|
-
|
47
|
+
# _LOG.debug("ff '%s'", ff)
|
48
|
+
for pattern in itertools.chain(self.protect_local, self.protect_recursive):
|
35
49
|
if os.sep in str(pattern):
|
36
|
-
#
|
50
|
+
# _LOG.debug("Pattern '%s' has path sep", pattern)
|
37
51
|
assert os.path.isabs(ff), f"Expected absolute path, got '{ff}'"
|
52
|
+
|
53
|
+
# Search against full path
|
38
54
|
if pattern.search(os.fspath(ff)):
|
39
55
|
return pattern
|
40
56
|
|
57
|
+
# Attempt exact match against path relative to , i.e. if pattern starts with '^'.
|
58
|
+
# This makes sense for patterns specified on commandline
|
59
|
+
cwd = os.getcwd()
|
60
|
+
ff_relative = str(Path(ff).relative_to(cwd))
|
61
|
+
# _LOG.debug("ff '%s' relative to start dir'%s'", ff_relative, cwd)
|
62
|
+
|
63
|
+
if pattern.match(ff_relative):
|
64
|
+
return pattern
|
65
|
+
|
41
66
|
elif pattern.search(ff.name):
|
42
67
|
return pattern
|
43
68
|
|
@@ -45,8 +70,8 @@ class DirConfig():
|
|
45
70
|
|
46
71
|
def __json__(self):
|
47
72
|
return {
|
48
|
-
|
49
|
-
"
|
73
|
+
DirConfig.__name__: super().__json__()[ProtectConfig.__name__] | {
|
74
|
+
"protect_local": [str(pat) for pat in self.protect_local],
|
50
75
|
"config_dir": str(self.config_dir),
|
51
76
|
"config_files": self.config_files,
|
52
77
|
}
|
@@ -113,15 +138,16 @@ class ConfigFiles():
|
|
113
138
|
protect: An optional sequence of regexes to be added to protect[recursive] for all directories.
|
114
139
|
ignore_config_dirs_config_files: Ignore config files in standard config directories.
|
115
140
|
ignore_per_directory_config_files: Ignore config files in collected directories.
|
116
|
-
remember_configs: Store loaded and merged configs in `
|
141
|
+
remember_configs: Store loaded and merged configs in `per_dir_configs` member variable.
|
117
142
|
app_dirs: Provide your own instance of AppDirs in addition to or as a replacement of the default to add config file names and path.
|
118
143
|
Configuration from later entries have higher precedence.
|
119
144
|
Note that if no AppDirs are specified, no config files will be loaded, neither from config dirs, nor from collected directories.
|
120
145
|
See: https://pypi.org/project/appdirs/
|
121
146
|
|
122
147
|
Members:
|
123
|
-
|
124
|
-
|
148
|
+
conf_file_names: File names which are config files.
|
149
|
+
remember_configs: Whether per directory resolved/merged configs are stored in `per_dir_configs`.
|
150
|
+
per_dir_configs: Mapping from dir name to directory specific config dict. Only if remember_configs is True.
|
125
151
|
"""
|
126
152
|
|
127
153
|
default_appdirs: AppDirs = AppDirs("file_groups", "Hupfeldt_IT")
|
@@ -131,7 +157,7 @@ class ConfigFiles():
|
|
131
157
|
_valid_dir_protect_scopes = ("local", "recursive")
|
132
158
|
_valid_config_dir_protect_scopes = ("local", "recursive", "global")
|
133
159
|
|
134
|
-
def __init__(
|
160
|
+
def __init__( # pylint: disable=too-many-positional-arguments,too-many-arguments
|
135
161
|
self, protect: Sequence[re.Pattern] = (),
|
136
162
|
ignore_config_dirs_config_files=False, ignore_per_directory_config_files=False, remember_configs=True,
|
137
163
|
app_dirs: Sequence[AppDirs]|None = None,
|
@@ -140,38 +166,34 @@ class ConfigFiles():
|
|
140
166
|
):
|
141
167
|
super().__init__()
|
142
168
|
|
143
|
-
self._global_config =
|
144
|
-
"local": set(),
|
145
|
-
"recursive": set(protect),
|
146
|
-
}, None, ())
|
147
|
-
|
169
|
+
self._global_config = ProtectConfig(set(protect))
|
148
170
|
self.remember_configs = remember_configs
|
149
|
-
self.per_dir_configs: dict[
|
171
|
+
self.per_dir_configs: dict[Path, DirConfig] = {} # key is abs_dir_path
|
150
172
|
self.ignore_per_directory_config_files = ignore_per_directory_config_files
|
151
173
|
|
152
174
|
app_dirs = app_dirs or (ConfigFiles.default_appdirs,)
|
153
|
-
self.
|
154
|
-
_LOG.debug("Conf file names: %s", self.
|
175
|
+
self.conf_file_name_pairs = tuple((apd.appname + ".conf", "." + apd.appname + ".conf") for apd in app_dirs)
|
176
|
+
_LOG.debug("Conf file names: %s", self.conf_file_name_pairs)
|
177
|
+
self.conf_file_names = list(itertools.chain.from_iterable(self.conf_file_name_pairs))
|
178
|
+
|
179
|
+
self.ignore_config_dirs_config_files = ignore_config_dirs_config_files
|
155
180
|
self.config_dirs = []
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
self.config_dirs.append(appd.user_config_dir)
|
181
|
+
for appd in app_dirs:
|
182
|
+
self.config_dirs.extend(appd.site_config_dir.split(':'))
|
183
|
+
for appd in app_dirs:
|
184
|
+
self.config_dirs.append(appd.user_config_dir)
|
161
185
|
|
162
186
|
self.config_file = config_file
|
163
187
|
|
164
188
|
# self.default_config_file_example = self.default_config_file.with_suffix('.example.py')
|
165
189
|
|
166
|
-
def
|
167
|
-
self, conf_dir: Path, conf_file_name_pair: Sequence[str],
|
168
|
-
) -> Tuple[dict[str,
|
169
|
-
"""Read config file
|
190
|
+
def _read_and_eval_config_file_for_one_appname(
|
191
|
+
self, conf_dir: Path, conf_file_name_pair: Sequence[str], ignore_config_files: bool
|
192
|
+
) -> Tuple[dict[str, Any], Path|None]:
|
193
|
+
"""Read config file.
|
170
194
|
|
171
195
|
Error if config files are found both with and withput '.' prefix.
|
172
|
-
|
173
|
-
|
174
|
-
Return: merged config dict with compiled regexes, config file name. If no config files is found, then return inherited parent conf and None.
|
196
|
+
Return: Config dict, config file name, or if no config files is found, None, None.
|
175
197
|
"""
|
176
198
|
|
177
199
|
assert conf_dir.is_absolute()
|
@@ -180,27 +202,41 @@ class ConfigFiles():
|
|
180
202
|
match [conf_dir/cfn for cfn in conf_file_name_pair if (conf_dir/cfn).exists()]:
|
181
203
|
case []:
|
182
204
|
_LOG.debug("No config file in directory %s", conf_dir)
|
183
|
-
|
184
|
-
"local": set(),
|
185
|
-
"recursive": parent_conf.protect["recursive"]
|
186
|
-
}
|
187
|
-
return no_conf_file, None
|
205
|
+
return {}, None
|
188
206
|
|
189
207
|
case [conf_file]:
|
190
208
|
if ignore_config_files:
|
191
209
|
_LOG.debug("Ignoring config file: %s", conf_file)
|
192
|
-
return
|
210
|
+
return {}, None
|
193
211
|
|
194
212
|
_LOG.debug("Read config file: %s", conf_file)
|
195
213
|
with open(conf_file, encoding="utf-8") as fh:
|
196
214
|
new_config = ast.literal_eval(fh.read())
|
197
215
|
_LOG.debug("%s", pformat(new_config))
|
216
|
+
return new_config, conf_file
|
198
217
|
|
199
218
|
case config_files:
|
200
219
|
msg = f"More than one config file in dir '{conf_dir}': {[cf.name for cf in config_files]}."
|
201
220
|
_LOG.debug("%s", msg)
|
202
221
|
raise ConfigException(msg)
|
203
222
|
|
223
|
+
def _read_and_validate_config_file_for_one_appname(
|
224
|
+
self, conf_dir: Path, conf_file_name_pair: Sequence[str], valid_protect_scopes: Tuple[str, ...], ignore_config_files: bool
|
225
|
+
) -> Tuple[dict[str, set[re.Pattern]], Path|None]:
|
226
|
+
"""Read config file, validate keys and compile regexes.
|
227
|
+
|
228
|
+
Error if config files are found both with and withput '.' prefix.
|
229
|
+
|
230
|
+
Return: merged config dict with compiled regexes, config file name. If no config files is found, then return empty sets and None.
|
231
|
+
"""
|
232
|
+
|
233
|
+
new_config, conf_file = self._read_and_eval_config_file_for_one_appname(conf_dir, conf_file_name_pair, ignore_config_files)
|
234
|
+
if not new_config:
|
235
|
+
return {
|
236
|
+
"local": set(),
|
237
|
+
"recursive": set(),
|
238
|
+
}, None
|
239
|
+
|
204
240
|
try:
|
205
241
|
protect_conf: dict[str, set[re.Pattern]] = new_config[self._fg_key][self._protect_key]
|
206
242
|
except KeyError as ex:
|
@@ -213,8 +249,6 @@ class ConfigFiles():
|
|
213
249
|
raise ConfigException(msg)
|
214
250
|
|
215
251
|
protect_conf[key] = set(re.compile(pattern) for pattern in val)
|
216
|
-
if key == "recursive":
|
217
|
-
protect_conf[key].update(parent_conf.protect[key])
|
218
252
|
|
219
253
|
for key in self._valid_dir_protect_scopes: # Do NOT use the 'valid_protect_scopes' argument here
|
220
254
|
protect_conf.setdefault(key, set())
|
@@ -223,55 +257,34 @@ class ConfigFiles():
|
|
223
257
|
if _LOG.isEnabledFor(lvl):
|
224
258
|
_LOG.log(lvl, "Merged directory config:\n%s", pformat(new_config))
|
225
259
|
|
226
|
-
return protect_conf, conf_file
|
227
|
-
|
228
|
-
def _read_and_validate_config_files(
|
229
|
-
self, conf_dir: Path, parent_conf: DirConfig, valid_protect_scopes: Tuple[str, ...], ignore_config_files: bool) -> DirConfig:
|
230
|
-
cfg_merge: dict[str, set[re.Pattern]] = {}
|
231
|
-
cfg_files: list[str] = []
|
232
|
-
for conf_file_name_pair in self.conf_file_names:
|
233
|
-
cfg, cfg_file = self._read_and_validate_config_file_for_one_appname(
|
234
|
-
conf_dir, conf_file_name_pair, parent_conf, valid_protect_scopes, ignore_config_files)
|
235
|
-
for key, val in cfg.items():
|
236
|
-
cfg_merge.setdefault(key, set()).update(val)
|
237
|
-
if cfg_file:
|
238
|
-
cfg_files.append(cfg_file)
|
239
|
-
|
240
|
-
return DirConfig(cfg_merge, conf_dir, cfg_files)
|
260
|
+
return protect_conf, conf_file
|
241
261
|
|
242
262
|
def load_config_dir_files(self) -> None:
|
243
263
|
"""Load config files from platform standard directories and specified config file, if any."""
|
244
264
|
|
245
|
-
|
246
|
-
|
247
|
-
|
265
|
+
if not self.ignore_config_dirs_config_files:
|
266
|
+
_LOG.debug("config_dirs: %s", self.config_dirs)
|
267
|
+
for conf_dir in self.config_dirs:
|
268
|
+
conf_dir = Path(conf_dir)
|
269
|
+
if not conf_dir.exists():
|
270
|
+
continue
|
248
271
|
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
del new_config.protect['global']
|
254
|
-
except KeyError:
|
255
|
-
pass
|
256
|
-
|
257
|
-
_LOG.debug("config_dirs: %s", self.config_dirs)
|
258
|
-
for conf_dir in self.config_dirs:
|
259
|
-
conf_dir = Path(conf_dir)
|
260
|
-
if not conf_dir.exists():
|
261
|
-
continue
|
262
|
-
|
263
|
-
new_config = self._read_and_validate_config_files(conf_dir, self._global_config, self._valid_config_dir_protect_scopes, False)
|
264
|
-
merge_one_config_to_global(conf_dir, new_config)
|
272
|
+
for conf_file_name_pair in self.conf_file_name_pairs:
|
273
|
+
cfg, _ = self._read_and_validate_config_file_for_one_appname(
|
274
|
+
conf_dir, conf_file_name_pair, self._valid_config_dir_protect_scopes, self.ignore_config_dirs_config_files)
|
275
|
+
self._global_config.protect_recursive |= cfg.get("global", set())
|
265
276
|
|
266
277
|
if self.config_file:
|
278
|
+
_LOG.debug("specified config_file: %s", self.config_file)
|
267
279
|
conf_dir = self.config_file.parent.absolute()
|
268
280
|
conf_name = self.config_file.name
|
269
|
-
cfg,
|
270
|
-
conf_dir, (conf_name,), self.
|
271
|
-
if not
|
281
|
+
cfg, fpath = self._read_and_validate_config_file_for_one_appname(
|
282
|
+
conf_dir, (conf_name,), self._valid_config_dir_protect_scopes, self.ignore_config_dirs_config_files)
|
283
|
+
if not fpath:
|
272
284
|
raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), str(self.config_file))
|
285
|
+
self._global_config.protect_recursive |= cfg.get("global", set())
|
273
286
|
|
274
|
-
|
287
|
+
_LOG.debug("Merged global config:\n %s", self._global_config)
|
275
288
|
|
276
289
|
def dir_config(self, conf_dir: Path, parent_conf: DirConfig|None) -> DirConfig:
|
277
290
|
"""Read and merge config file from directory 'conf_dir' with 'parent_conf'.
|
@@ -279,12 +292,24 @@ class ConfigFiles():
|
|
279
292
|
If directory has no parent in the file_groups included dirs, then None should be supplied as parent_conf.
|
280
293
|
"""
|
281
294
|
|
282
|
-
|
283
|
-
|
295
|
+
cfg_merge_local: set[re.Pattern] = set()
|
296
|
+
cfg_merge_recursive: set[re.Pattern] = set()
|
297
|
+
cfg_files: list[str] = []
|
298
|
+
|
299
|
+
for conf_file_name_pair in self.conf_file_name_pairs:
|
300
|
+
cfg, cfg_file = self._read_and_validate_config_file_for_one_appname(
|
301
|
+
conf_dir, conf_file_name_pair, self._valid_dir_protect_scopes, self.ignore_per_directory_config_files)
|
302
|
+
cfg_merge_local.update(cfg.get("local", set()))
|
303
|
+
cfg_merge_recursive.update(cfg.get("recursive", set()))
|
304
|
+
if cfg_file:
|
305
|
+
cfg_files.append(cfg_file.name)
|
306
|
+
|
307
|
+
parent_protect_recursive = parent_conf.protect_recursive if parent_conf else self._global_config.protect_recursive
|
308
|
+
new_config = DirConfig(cfg_merge_recursive | parent_protect_recursive, cfg_merge_local, conf_dir, cfg_files)
|
284
309
|
_LOG.debug("new_config:\n %s", new_config)
|
285
310
|
|
286
311
|
if self.remember_configs:
|
287
|
-
self.per_dir_configs[
|
312
|
+
self.per_dir_configs[conf_dir] = new_config
|
288
313
|
# _LOG.debug("per_dir_configs:\n %s", self.per_dir_configs)
|
289
314
|
|
290
315
|
return new_config
|
file_groups/groups.py
CHANGED
@@ -7,7 +7,7 @@ from dataclasses import dataclass
|
|
7
7
|
from itertools import chain
|
8
8
|
from enum import Enum
|
9
9
|
import logging
|
10
|
-
from typing import Sequence
|
10
|
+
from typing import Sequence, cast
|
11
11
|
|
12
12
|
from .config_files import DirConfig, ConfigFiles
|
13
13
|
|
@@ -89,7 +89,7 @@ class FileGroups():
|
|
89
89
|
Note: Since these files are excluded from protection, it means they er NOT protected!
|
90
90
|
work_include: ONLY include files matching regex in the may_work_on files (does not apply to symlinks). Default: Include ALL.
|
91
91
|
|
92
|
-
config_files: Load config files. See config_files.ConfigFiles.
|
92
|
+
config_files: Load config files. See config_files.ConfigFiles. Note that the default 'None' means use the `config_files.ConfigFiles` class with default arguments.
|
93
93
|
"""
|
94
94
|
|
95
95
|
def __init__(
|
@@ -169,8 +169,46 @@ class FileGroups():
|
|
169
169
|
|
170
170
|
checked_dirs: set[str] = set()
|
171
171
|
|
172
|
+
def handle_entry(abs_dir_path: str, group: _Group, other_group: _Group, dir_config: DirConfig, entry: DirEntry):
|
173
|
+
"""Put entry in the correct group. Call 'find_group' if entry is a directory."""
|
174
|
+
if group.typ is GroupType.MAY_WORK_ON:
|
175
|
+
# Check for match against configured protect patterns, if match, then the file must got to protect group instead
|
176
|
+
pattern = dir_config.is_protected(entry)
|
177
|
+
if pattern:
|
178
|
+
_LOG.debug("find %s - '%s' is protected by regex %s, assigning to group %s instead.", group.typ.name, entry.path, pattern, other_group.typ.name)
|
179
|
+
group, other_group = other_group, group
|
180
|
+
|
181
|
+
if entry.is_dir(follow_symlinks=False):
|
182
|
+
if entry.path in other_group.dirs:
|
183
|
+
_LOG.debug("find %s - '%s' is in '%s' dir list and not in '%s' dir list", group.typ.name, entry.path, other_group.typ.name, group.typ.name)
|
184
|
+
find_group(entry.path, other_group, group, dir_config)
|
185
|
+
return
|
186
|
+
|
187
|
+
find_group(entry.path, group, other_group, dir_config)
|
188
|
+
return
|
189
|
+
|
190
|
+
if entry.name in self.config_files.conf_file_names:
|
191
|
+
return
|
192
|
+
|
193
|
+
if entry.is_symlink():
|
194
|
+
# cast: https://github.com/python/mypy/issues/11964
|
195
|
+
points_to = os.readlink(cast(str, entry))
|
196
|
+
abs_points_to = os.path.normpath(os.path.join(abs_dir_path, points_to))
|
197
|
+
|
198
|
+
if entry.is_dir(follow_symlinks=True):
|
199
|
+
_LOG.debug("find %s - '%s' -> '%s' is a symlink to a directory - ignoring", group.typ.name, entry.path, points_to)
|
200
|
+
group.num_directory_symlinks += 1
|
201
|
+
return
|
202
|
+
|
203
|
+
group.symlinks[entry.path] = entry
|
204
|
+
group.symlinks_by_abs_points_to[abs_points_to].append(entry)
|
205
|
+
return
|
206
|
+
|
207
|
+
_LOG.debug("find %s - entry name: %s", group.typ.name, entry.name)
|
208
|
+
group.add_entry_match(entry)
|
209
|
+
|
172
210
|
def find_group(abs_dir_path: str, group: _Group, other_group: _Group, parent_conf: DirConfig|None):
|
173
|
-
"""
|
211
|
+
"""Recursively find all files belonging to 'group'"""
|
174
212
|
_LOG.debug("find %s: %s", group.typ.name, abs_dir_path)
|
175
213
|
if abs_dir_path in checked_dirs:
|
176
214
|
_LOG.debug("directory already checked")
|
@@ -180,48 +218,14 @@ class FileGroups():
|
|
180
218
|
dir_config = self.config_files.dir_config(Path(abs_dir_path), parent_conf)
|
181
219
|
|
182
220
|
for entry in os.scandir(abs_dir_path):
|
183
|
-
|
184
|
-
if entry.path in other_group.dirs:
|
185
|
-
_LOG.debug("find %s - '%s' is in '%s' dir list and not in '%s' dir list", group.typ.name, entry.path, other_group.typ.name, group.typ.name)
|
186
|
-
find_group(entry.path, other_group, group, dir_config)
|
187
|
-
continue
|
188
|
-
|
189
|
-
find_group(entry.path, group, other_group, dir_config)
|
190
|
-
continue
|
191
|
-
|
192
|
-
if entry.name in dir_config.config_files:
|
193
|
-
continue
|
194
|
-
|
195
|
-
current_group = group
|
196
|
-
if group.typ is GroupType.MAY_WORK_ON:
|
197
|
-
# We need to check for match against configured protect patterns, if match, then the file must got to protect group instead
|
198
|
-
pattern = dir_config.is_protected(entry)
|
199
|
-
if pattern:
|
200
|
-
_LOG.debug("find %s - '%s' is protected by regex %s, assigning to group %s instead.", group.typ.name, entry.path, pattern, other_group.typ.name)
|
201
|
-
current_group = other_group
|
202
|
-
|
203
|
-
if entry.is_symlink():
|
204
|
-
points_to = os.readlink(entry)
|
205
|
-
abs_points_to = os.path.normpath(os.path.join(abs_dir_path, points_to))
|
206
|
-
|
207
|
-
if entry.is_dir(follow_symlinks=True):
|
208
|
-
_LOG.debug("find %s - '%s' -> '%s' is a symlink to a directory - ignoring", current_group.typ.name, entry.path, points_to)
|
209
|
-
current_group.num_directory_symlinks += 1
|
210
|
-
continue
|
211
|
-
|
212
|
-
current_group.symlinks[entry.path] = entry
|
213
|
-
current_group.symlinks_by_abs_points_to[abs_points_to].append(entry)
|
214
|
-
continue
|
215
|
-
|
216
|
-
_LOG.debug("find %s - entry name: %s", current_group.typ.name, entry.name)
|
217
|
-
current_group.add_entry_match(entry)
|
221
|
+
handle_entry(abs_dir_path, group, other_group, dir_config, entry)
|
218
222
|
|
219
223
|
checked_dirs.add(abs_dir_path)
|
220
224
|
|
221
225
|
for any_dir in sorted(chain(self.must_protect.dirs, self.may_work_on.dirs), key=lambda dd: len(Path(dd).parts)):
|
222
226
|
parent_dir = Path(any_dir)
|
223
227
|
while len(parent_dir.parts) > 1:
|
224
|
-
parent_conf = self.config_files.per_dir_configs.get(
|
228
|
+
parent_conf = self.config_files.per_dir_configs.get(parent_dir)
|
225
229
|
if parent_conf:
|
226
230
|
break
|
227
231
|
|
file_groups/handler.py
CHANGED
@@ -28,7 +28,7 @@ class FileHandler(FileGroups):
|
|
28
28
|
they could have logically been made to point to a file in a protect dir.
|
29
29
|
"""
|
30
30
|
|
31
|
-
def __init__(
|
31
|
+
def __init__( # pylint: disable=too-many-arguments
|
32
32
|
self,
|
33
33
|
protect_dirs_seq: Sequence[Path], work_dirs_seq: Sequence[Path],
|
34
34
|
*,
|
file_groups/handler_compare.py
CHANGED
@@ -22,7 +22,7 @@ class FileHandlerCompare(FileHandler):
|
|
22
22
|
fcmp: Object providing compare function.
|
23
23
|
"""
|
24
24
|
|
25
|
-
def __init__(
|
25
|
+
def __init__( # pylint: disable=too-many-arguments
|
26
26
|
self,
|
27
27
|
protect_dirs_seq: Sequence[Path], work_dirs_seq: Sequence[Path], fcmp: CompareFiles,
|
28
28
|
*,
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
|
-
Name:
|
3
|
-
Version: 0.3.
|
2
|
+
Name: file_groups
|
3
|
+
Version: 0.3.1
|
4
4
|
Summary: Group files into 'protect' and 'work_on' and provide operations for safe delete/move and symlink handling.
|
5
5
|
Home-page: https://github.com/lhupfeldt/file_groups.git
|
6
6
|
Author: Lars Hupfeldt Nielsen
|
@@ -11,16 +11,17 @@ Classifier: Intended Audience :: Developers
|
|
11
11
|
Classifier: License :: OSI Approved :: BSD License
|
12
12
|
Classifier: Natural Language :: English
|
13
13
|
Classifier: Operating System :: OS Independent
|
14
|
-
Classifier: Programming Language :: Python :: 3.
|
15
|
-
Classifier: Programming Language :: Python :: 3.11
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
16
15
|
Classifier: Programming Language :: Python :: 3.12
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
17
18
|
Classifier: Topic :: Software Development :: Libraries
|
18
19
|
Requires-Python: >=3.10
|
19
20
|
Description-Content-Type: text/x-rst
|
20
21
|
License-File: LICENSE.txt
|
21
|
-
Requires-Dist: appdirs
|
22
|
+
Requires-Dist: appdirs>=1.4.4
|
22
23
|
Provides-Extra: dev
|
23
|
-
Requires-Dist: nox
|
24
|
+
Requires-Dist: nox; extra == "dev"
|
24
25
|
|
25
26
|
Library for grouping files into sets of 'work-on' and 'protect' based on arbitrarily nested directories.
|
26
27
|
|
@@ -0,0 +1,14 @@
|
|
1
|
+
file_groups/__init__.py,sha256=XBp5563xhTer1vQY-DpKRvViAvlHaZqx9GrINiGqbeI,127
|
2
|
+
file_groups/compare_files.py,sha256=38EfZzpEpj0wH7SUruPxgyF_W8eqCbIjG6yUOx8hcFA,434
|
3
|
+
file_groups/config_files.py,sha256=VwRmZYEemKQawYI43qV9XhvPyI7ouaDjjadhc74wqNs,14021
|
4
|
+
file_groups/groups.py,sha256=Lt8QlAET8032t9OXcq9cmzLy27tZsYTnLAaGilzEtcM,11377
|
5
|
+
file_groups/handler.py,sha256=NmYfmPLGXgPsGxM9uR2r8aS3cI-8S7D5-8884EoGA1M,10083
|
6
|
+
file_groups/handler_compare.py,sha256=qbGHwT0FSvfnWtAnjYi4px6V5xIM2cStm0soflwYAi4,2174
|
7
|
+
file_groups/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
8
|
+
file_groups/types.py,sha256=giq3H58RuKpS6b7sjxnzP-sN1Xr4VTQ-rG4brLP5qMI,107
|
9
|
+
file_groups-0.3.1.dist-info/LICENSE.txt,sha256=NZc9ictLEwfoL4ywpAbLbd-n02KETGHLYQU820TJD_Q,1494
|
10
|
+
file_groups-0.3.1.dist-info/METADATA,sha256=wSlWiFxs8yMgFvY6jN3pWIBGBVbbneRhyMQM5tNcoPw,1561
|
11
|
+
file_groups-0.3.1.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
12
|
+
file_groups-0.3.1.dist-info/top_level.txt,sha256=mAdRf0R2TA8tpH7ftHzXP7VMRhw07p7B2mI8QKpDnjU,12
|
13
|
+
file_groups-0.3.1.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
14
|
+
file_groups-0.3.1.dist-info/RECORD,,
|
@@ -1,14 +0,0 @@
|
|
1
|
-
file_groups/__init__.py,sha256=1OO3tLnRnIe51VfZSQz0sqUaUP-xS7oHxiSexnKwpEY,94
|
2
|
-
file_groups/compare_files.py,sha256=38EfZzpEpj0wH7SUruPxgyF_W8eqCbIjG6yUOx8hcFA,434
|
3
|
-
file_groups/config_files.py,sha256=kU1wTANXjk5H95x1TpxIBFZrobJFjTMbQ_mjpnewh-U,13011
|
4
|
-
file_groups/groups.py,sha256=R3wF8DV3rhMckjgz6wuycxJ9UxAB2p56zzYcdbh72M0,11072
|
5
|
-
file_groups/handler.py,sha256=X_uFNAMtuiBro_8WG6XFEq_Ys4L8175tSP_gajA01fo,10045
|
6
|
-
file_groups/handler_compare.py,sha256=J6Jev9ev9oECzYCD9M6FUlkKMMMkaP2YlygG11lKqPI,2136
|
7
|
-
file_groups/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
8
|
-
file_groups/types.py,sha256=giq3H58RuKpS6b7sjxnzP-sN1Xr4VTQ-rG4brLP5qMI,107
|
9
|
-
file_groups-0.3.0.dist-info/LICENSE.txt,sha256=NZc9ictLEwfoL4ywpAbLbd-n02KETGHLYQU820TJD_Q,1494
|
10
|
-
file_groups-0.3.0.dist-info/METADATA,sha256=BseS9ayqXIwuDBBUb95mPQUcLmrHbmHLO7mSGOr9IEQ,1512
|
11
|
-
file_groups-0.3.0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
12
|
-
file_groups-0.3.0.dist-info/top_level.txt,sha256=mAdRf0R2TA8tpH7ftHzXP7VMRhw07p7B2mI8QKpDnjU,12
|
13
|
-
file_groups-0.3.0.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
14
|
-
file_groups-0.3.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|