file-groups 0.2.1__py3-none-any.whl → 0.3.0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,11 +1,13 @@
1
1
  import ast
2
2
  import os
3
+ import errno
3
4
  import re
4
5
  from pathlib import Path
5
6
  import itertools
6
7
  from pprint import pformat
7
8
  import logging
8
- from typing import Mapping, Tuple, Sequence, cast
9
+ from dataclasses import dataclass
10
+ from typing import Tuple, Sequence, cast
9
11
 
10
12
  from appdirs import AppDirs # type: ignore
11
13
 
@@ -19,11 +21,49 @@ class ConfigException(Exception):
19
21
  """Invalid configuration"""
20
22
 
21
23
 
24
+ @dataclass
25
+ class DirConfig():
26
+ """Hold protect config for a directory, or global (site or user) config."""
27
+ protect: dict[str, set[re.Pattern]]
28
+ config_dir: Path|None
29
+ config_files: Sequence[str]
30
+
31
+ def is_protected(self, ff: FsPath):
32
+ """If ff id protected by a regex pattern then return the pattern, otherwise return None."""
33
+
34
+ for pattern in itertools.chain(self.protect["local"], self.protect["recursive"]):
35
+ if os.sep in str(pattern):
36
+ # Match against full path
37
+ assert os.path.isabs(ff), f"Expected absolute path, got '{ff}'"
38
+ if pattern.search(os.fspath(ff)):
39
+ return pattern
40
+
41
+ elif pattern.search(ff.name):
42
+ return pattern
43
+
44
+ return None
45
+
46
+ def __json__(self):
47
+ return {
48
+ "DirConfig": {
49
+ "protect": {key: list(str(pat) for pat in val) for key, val in self.protect.items()},
50
+ "config_dir": str(self.config_dir),
51
+ "config_files": self.config_files,
52
+ }
53
+ }
54
+
55
+
22
56
  class ConfigFiles():
23
57
  r"""Handle config files.
24
58
 
25
59
  Config files are searched for in the standard config directories on the platform AND in any collected directory.
26
- Config files in config directories must be named 'file_groups.conf' and in collected directories '.file_groups.conf' or 'file_groups.conf'.
60
+
61
+ The 'app_dirs' default sets default config dirs and config-file names.
62
+ It is also possible to specify additional or alternative config files specific to the application using this library.
63
+ Config files must be named after the AppDirs.appname (first argument) as <appname>.conf or .<appname>.conf.
64
+ The defaults are 'file_groups.conf' and '.file_groups.conf'.
65
+ You should consider carefully before disabling loading of the default files, as an end user likely wants the protection rules to apply for any
66
+ application using this library.
27
67
 
28
68
  The content of a conf file is a Python dict with the following structure.
29
69
 
@@ -56,14 +96,14 @@ class ConfigFiles():
56
96
  }
57
97
  }
58
98
 
59
- The level one keys (e.g. 'file_groups') are the application (library) names.
60
- Applications are free to add entries at this level.
99
+ The level one key is 'file_groups'.
100
+ Applications are free to add entries at this level, but not underneath. This is protect against ignored misspelled keys.
61
101
 
62
102
  The 'file_groups' entry is a dict with a single 'protect' entry.
63
103
  The 'protect' entry is a dict with at most three entries: 'local', 'recursive' and 'global'. These specify whether a directory specific
64
104
  configuration will inherit and extend the parent (and global) config, or whether it is local to current directory only.
65
105
  The 'local', 'recursive' and 'global' entries are lists of regex patterns to match against collected 'work_on' files.
66
- Regexes are checked against the simple filename (i.e. not the full path) unless they contain at least one path separator (os.sep), in
106
+ Regexes are checked against the simple file name (i.e. not the full path) unless they contain at least one path separator (os.sep), in
67
107
  which case they are checked against the absolute path.
68
108
  All checks are done as regex *search* (better to protect too much than too little). Write the regex to match the full name or path if needed.
69
109
 
@@ -74,16 +114,17 @@ class ConfigFiles():
74
114
  ignore_config_dirs_config_files: Ignore config files in standard config directories.
75
115
  ignore_per_directory_config_files: Ignore config files in collected directories.
76
116
  remember_configs: Store loaded and merged configs in `dir_configs` member variable.
77
- app_dirs: AppDirs("file_groups", "Hupfeldt_IT"), Provide your own instance to change congig file names and path.
117
+ 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
+ Configuration from later entries have higher precedence.
119
+ Note that if no AppDirs are specified, no config files will be loaded, neither from config dirs, nor from collected directories.
78
120
  See: https://pypi.org/project/appdirs/
79
121
 
80
122
  Members:
81
- global_config: dict
82
123
  remember_configs: Whether per directory resolved/merged configs are stored in `dir_configs`.
83
124
  dir_configs: dict[str: dict] Mapping from dir name to directory specific config dict. Only if remember_configs is True.
84
125
  """
85
126
 
86
- conf_file_names = [".file_groups.conf", "file_groups.conf"]
127
+ default_appdirs: AppDirs = AppDirs("file_groups", "Hupfeldt_IT")
87
128
 
88
129
  _fg_key = "file_groups"
89
130
  _protect_key = "protect"
@@ -92,107 +133,76 @@ class ConfigFiles():
92
133
 
93
134
  def __init__(
94
135
  self, protect: Sequence[re.Pattern] = (),
95
- ignore_config_dirs_config_files=False, ignore_per_directory_config_files=False, remember_configs=False,
96
- app_dirs=None):
136
+ ignore_config_dirs_config_files=False, ignore_per_directory_config_files=False, remember_configs=True,
137
+ app_dirs: Sequence[AppDirs]|None = None,
138
+ *,
139
+ config_file: Path|None = None,
140
+ ):
97
141
  super().__init__()
98
- self.remember_configs = remember_configs
99
142
 
100
- self.per_dir_configs: dict[str, dict] = {} # key is abs_dir_path, value is config dict
101
- self.global_config = {
102
- "file_groups": {
103
- "protect": {
104
- "local": set(),
105
- "recursive": set(protect),
106
- }
107
- }
108
- }
143
+ self._global_config = DirConfig({
144
+ "local": set(),
145
+ "recursive": set(protect),
146
+ }, None, ())
109
147
 
148
+ self.remember_configs = remember_configs
149
+ self.per_dir_configs: dict[str, DirConfig] = {} # key is abs_dir_path
110
150
  self.ignore_per_directory_config_files = ignore_per_directory_config_files
111
151
 
152
+ app_dirs = app_dirs or (ConfigFiles.default_appdirs,)
153
+ self.conf_file_names = tuple((apd.appname + ".conf", "." + apd.appname + ".conf") for apd in app_dirs)
154
+ _LOG.debug("Conf file names: %s", self.conf_file_names)
155
+ self.config_dirs = []
112
156
  if not ignore_config_dirs_config_files:
113
- self._load_config_dir_files(app_dirs or AppDirs("file_groups", "Hupfeldt_IT"))
114
-
115
- def _load_config_dir_files(self, app_dirs):
116
- config_dirs = app_dirs.site_config_dir.split(':') + [app_dirs.user_config_dir]
117
- _LOG.debug("config_dirs: %s", config_dirs)
118
- gfpt = self.global_config["file_groups"]["protect"]
119
- for conf_dir in config_dirs:
120
- conf_dir = Path(conf_dir)
121
- if not conf_dir.exists():
122
- continue
157
+ for appd in app_dirs:
158
+ self.config_dirs.extend(appd.site_config_dir.split(':'))
159
+ for appd in app_dirs:
160
+ self.config_dirs.append(appd.user_config_dir)
123
161
 
124
- new_config, _ = self._read_and_validate_config_file(conf_dir, self.global_config, self._valid_config_dir_protect_scopes, False)
125
- if self.remember_configs:
126
- self.per_dir_configs[str(conf_dir)] = new_config
162
+ self.config_file = config_file
127
163
 
128
- fpt = new_config["file_groups"]["protect"]
129
- cast(set, gfpt["recursive"]).update(fpt.get("global", ()))
130
- _LOG.debug("Merged global config:\n %s", pformat(new_config))
164
+ # self.default_config_file_example = self.default_config_file.with_suffix('.example.py')
131
165
 
132
- try:
133
- del fpt['global']
134
- except KeyError:
135
- pass
136
-
137
- # self.default_config_file_example = self.default_config_file.with_suffix('.example.py')
138
-
139
- def _get_single_conf_file(self, conf_dir: Path, ignore_config_files: bool) -> Tuple[dict|None, Path|None]:
140
- """Return the config file content and path if any config file is found in conf_dir. Error if two are found."""
141
- _LOG.debug("Checking for config file in directory: %s", conf_dir)
142
-
143
- num_files = 0
144
- for cfn in self.conf_file_names:
145
- tmp_conf_file = conf_dir/cfn
146
- if tmp_conf_file.exists():
147
- conf_file = tmp_conf_file
148
- num_files += 1
149
-
150
- if num_files == 1:
151
- if ignore_config_files:
152
- _LOG.debug("Ignoring config file: %s", conf_file)
153
- return None, None
154
-
155
- _LOG.debug("Read config file: %s", conf_file)
156
- with open(conf_file, encoding="utf-8") as fh:
157
- new_config = ast.literal_eval(fh.read())
158
- _LOG.debug("%s", pformat(new_config))
159
- return new_config, conf_file
160
-
161
- if num_files == 0:
162
- _LOG.debug("No config file in directory %s", conf_dir)
163
- return None, None
164
-
165
- msg = f"More than one config file in dir '{conf_dir}': {self.conf_file_names}."
166
- _LOG.debug("%s", msg)
167
- raise ConfigException(msg)
168
-
169
- def _read_and_validate_config_file(
170
- self, conf_dir: Path, parent_conf: dict, valid_protect_scopes: Tuple[str, ...], ignore_config_files: bool
171
- ) -> Tuple[dict, Path|None]:
166
+ def _read_and_validate_config_file_for_one_appname( # pylint: disable=too-many-locals
167
+ self, conf_dir: Path, conf_file_name_pair: Sequence[str], parent_conf: DirConfig, valid_protect_scopes: Tuple[str, ...], ignore_config_files: bool
168
+ ) -> Tuple[dict[str, set[re.Pattern]], str|None]:
172
169
  """Read config file, validate keys and compile regexes and merge with parent.
173
170
 
171
+ Error if config files are found both with and withput '.' prefix.
174
172
  Merge parent conf into conf_dir conf (if any) and return the merged dict. The parent conf is not modified.
175
173
 
176
- Return: merged config dict with compiled regexes.
174
+ Return: merged config dict with compiled regexes, config file name. If no config files is found, then return inherited parent conf and None.
177
175
  """
178
176
 
179
177
  assert conf_dir.is_absolute()
178
+ _LOG.debug("Checking for config files %s in directory: %s", conf_file_name_pair, conf_dir)
180
179
 
181
- no_conf_file = {
182
- "file_groups": {
183
- "protect": {
180
+ match [conf_dir/cfn for cfn in conf_file_name_pair if (conf_dir/cfn).exists()]:
181
+ case []:
182
+ _LOG.debug("No config file in directory %s", conf_dir)
183
+ no_conf_file: dict[str, set[re.Pattern]] = {
184
184
  "local": set(),
185
- "recursive": parent_conf[self._fg_key][self._protect_key]["recursive"]
185
+ "recursive": parent_conf.protect["recursive"]
186
186
  }
187
- }
188
- }
187
+ return no_conf_file, None
188
+
189
+ case [conf_file]:
190
+ if ignore_config_files:
191
+ _LOG.debug("Ignoring config file: %s", conf_file)
192
+ return self._global_config.protect, None
193
+
194
+ _LOG.debug("Read config file: %s", conf_file)
195
+ with open(conf_file, encoding="utf-8") as fh:
196
+ new_config = ast.literal_eval(fh.read())
197
+ _LOG.debug("%s", pformat(new_config))
189
198
 
190
- new_config, conf_file = self._get_single_conf_file(conf_dir, ignore_config_files)
191
- if not new_config or ignore_config_files:
192
- return no_conf_file, None
199
+ case config_files:
200
+ msg = f"More than one config file in dir '{conf_dir}': {[cf.name for cf in config_files]}."
201
+ _LOG.debug("%s", msg)
202
+ raise ConfigException(msg)
193
203
 
194
204
  try:
195
- protect_conf = new_config[self._fg_key][self._protect_key]
205
+ protect_conf: dict[str, set[re.Pattern]] = new_config[self._fg_key][self._protect_key]
196
206
  except KeyError as ex:
197
207
  raise ConfigException(f"Config file '{conf_file}' is missing mandatory configuration '{self._fg_key}[{self._protect_key}]'.") from ex
198
208
 
@@ -204,7 +214,7 @@ class ConfigFiles():
204
214
 
205
215
  protect_conf[key] = set(re.compile(pattern) for pattern in val)
206
216
  if key == "recursive":
207
- protect_conf[key].update(parent_conf[self._fg_key][self._protect_key][key])
217
+ protect_conf[key].update(parent_conf.protect[key])
208
218
 
209
219
  for key in self._valid_dir_protect_scopes: # Do NOT use the 'valid_protect_scopes' argument here
210
220
  protect_conf.setdefault(key, set())
@@ -213,32 +223,68 @@ class ConfigFiles():
213
223
  if _LOG.isEnabledFor(lvl):
214
224
  _LOG.log(lvl, "Merged directory config:\n%s", pformat(new_config))
215
225
 
216
- return new_config, conf_file
226
+ return protect_conf, conf_file.name
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)
241
+
242
+ def load_config_dir_files(self) -> None:
243
+ """Load config files from platform standard directories and specified config file, if any."""
217
244
 
218
- def dir_config(self, conf_dir: Path, parent_conf: dict) -> Tuple[dict, Path|None]:
245
+ def merge_one_config_to_global(conf_dir, new_config):
246
+ if self.remember_configs:
247
+ self.per_dir_configs[str(conf_dir)] = new_config
248
+
249
+ self._global_config.protect["recursive"].update(new_config.protect.get("global", set()))
250
+ _LOG.debug("Merged global config:\n %s", new_config)
251
+
252
+ try:
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)
265
+
266
+ if self.config_file:
267
+ conf_dir = self.config_file.parent.absolute()
268
+ conf_name = self.config_file.name
269
+ cfg, filename = self._read_and_validate_config_file_for_one_appname(
270
+ conf_dir, (conf_name,), self._global_config, self._valid_config_dir_protect_scopes, ignore_config_files=False)
271
+ if not filename:
272
+ raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), str(self.config_file))
273
+
274
+ merge_one_config_to_global(conf_dir, DirConfig(cfg, conf_dir, (cast(str, filename),)))
275
+
276
+ def dir_config(self, conf_dir: Path, parent_conf: DirConfig|None) -> DirConfig:
219
277
  """Read and merge config file from directory 'conf_dir' with 'parent_conf'.
220
278
 
221
- If directory has no parent in the file_groups included dirs, then self.global_config must be supplied as parent_conf.
279
+ If directory has no parent in the file_groups included dirs, then None should be supplied as parent_conf.
222
280
  """
223
281
 
224
- new_config, conf_file = self._read_and_validate_config_file(
225
- conf_dir, parent_conf, self._valid_dir_protect_scopes, self.ignore_per_directory_config_files)
282
+ new_config = self._read_and_validate_config_files(
283
+ conf_dir, parent_conf or self._global_config, self._valid_dir_protect_scopes, self.ignore_per_directory_config_files)
284
+ _LOG.debug("new_config:\n %s", new_config)
285
+
226
286
  if self.remember_configs:
227
287
  self.per_dir_configs[str(conf_dir)] = new_config
228
- return new_config, conf_file
229
-
230
- def is_protected(self, ff: FsPath, dir_config: Mapping):
231
- """If ff id protected by a regex patterm then return the pattern, otherwise return None."""
232
-
233
- cfg_protected = dir_config[self._fg_key][self._protect_key]
234
- for pattern in itertools.chain(cfg_protected["local"], cfg_protected["recursive"]):
235
- if os.sep in str(pattern):
236
- # Match against full path
237
- assert os.path.isabs(ff), f"Expected absolute path, got '{ff}'"
238
- if pattern.search(os.fspath(ff)):
239
- return pattern
288
+ # _LOG.debug("per_dir_configs:\n %s", self.per_dir_configs)
240
289
 
241
- elif pattern.search(ff.name):
242
- return pattern
243
-
244
- return None
290
+ return new_config
file_groups/groups.py CHANGED
@@ -9,7 +9,7 @@ from enum import Enum
9
9
  import logging
10
10
  from typing import Sequence
11
11
 
12
- from .config_files import ConfigFiles
12
+ from .config_files import DirConfig, ConfigFiles
13
13
 
14
14
 
15
15
  _LOG = logging.getLogger(__name__)
@@ -87,27 +87,21 @@ class FileGroups():
87
87
 
88
88
  protect_exclude: Exclude files matching regex in the protected files (does not apply to symlinks). Default: Include ALL.
89
89
  Note: Since these files are excluded from protection, it means they er NOT protected!
90
- work_include: Only include files matching regex in the may_work_on files (does not apply to symlinks). Default: Include ALL.
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
- ignore_config_dirs_config_files: Ignore config files in standard config directories.
93
- ignore_per_directory_config_files: Ignore config files in collected directories.
92
+ config_files: Load config files. See config_files.ConfigFiles.
94
93
  """
95
94
 
96
95
  def __init__(
97
96
  self,
98
97
  protect_dirs_seq: Sequence[Path], work_dirs_seq: Sequence[Path],
99
98
  *,
100
- protect: Sequence[re.Pattern] = (),
101
99
  protect_exclude: re.Pattern|None = None, work_include: re.Pattern|None = None,
102
- ignore_config_dirs_config_files=False, ignore_per_directory_config_files=False,
103
- remember_configs=True):
100
+ config_files: ConfigFiles|None = None):
104
101
  super().__init__()
105
102
 
106
- self.config_files = ConfigFiles(
107
- protect=protect,
108
- ignore_config_dirs_config_files=ignore_config_dirs_config_files,
109
- ignore_per_directory_config_files=ignore_per_directory_config_files,
110
- remember_configs=remember_configs)
103
+ self.config_files = config_files or ConfigFiles()
104
+ self.config_files.load_config_dir_files()
111
105
 
112
106
  # Turn all paths into absolute paths with symlinks resolved, keep referrence to original argument for messages
113
107
  protect_dirs: dict[str, Path] = {os.path.abspath(os.path.realpath(kp)): kp for kp in protect_dirs_seq}
@@ -153,8 +147,8 @@ class FileGroups():
153
147
  top/d2/d1
154
148
  top/d2/d1/f1.jpg
155
149
 
156
- When: self.work_dirs_seq is [top, top/d1/d1]
157
- And: self.protect_dirs_seq is [top/d1]
150
+ When: work_dirs_seq is [top, top/d1/d1]
151
+ And: protect_dirs_seq is [top/d1]
158
152
 
159
153
  Then:
160
154
 
@@ -175,7 +169,7 @@ class FileGroups():
175
169
 
176
170
  checked_dirs: set[str] = set()
177
171
 
178
- def find_group(abs_dir_path: str, group: _Group, other_group: _Group, parent_conf: dict):
172
+ def find_group(abs_dir_path: str, group: _Group, other_group: _Group, parent_conf: DirConfig|None):
179
173
  """Find all files belonging to 'group'"""
180
174
  _LOG.debug("find %s: %s", group.typ.name, abs_dir_path)
181
175
  if abs_dir_path in checked_dirs:
@@ -183,7 +177,7 @@ class FileGroups():
183
177
  return
184
178
 
185
179
  group.num_directories += 1
186
- dir_config, config_file = self.config_files.dir_config(Path(abs_dir_path), parent_conf)
180
+ dir_config = self.config_files.dir_config(Path(abs_dir_path), parent_conf)
187
181
 
188
182
  for entry in os.scandir(abs_dir_path):
189
183
  if entry.is_dir(follow_symlinks=False):
@@ -195,13 +189,13 @@ class FileGroups():
195
189
  find_group(entry.path, group, other_group, dir_config)
196
190
  continue
197
191
 
198
- if config_file and entry.name == config_file.name:
192
+ if entry.name in dir_config.config_files:
199
193
  continue
200
194
 
201
195
  current_group = group
202
196
  if group.typ is GroupType.MAY_WORK_ON:
203
197
  # We need to check for match against configured protect patterns, if match, then the file must got to protect group instead
204
- pattern = self.config_files.is_protected(entry, dir_config)
198
+ pattern = dir_config.is_protected(entry)
205
199
  if pattern:
206
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)
207
201
  current_group = other_group
@@ -233,7 +227,7 @@ class FileGroups():
233
227
 
234
228
  parent_dir = parent_dir.parent
235
229
  else:
236
- parent_conf = self.config_files.global_config
230
+ parent_conf = None
237
231
 
238
232
  if any_dir in self.must_protect.dirs:
239
233
  find_group(any_dir, self.must_protect, self.may_work_on, parent_conf)
file_groups/handler.py CHANGED
@@ -7,6 +7,7 @@ import logging
7
7
  from typing import Sequence
8
8
 
9
9
  from .groups import FileGroups
10
+ from .config_files import ConfigFiles
10
11
 
11
12
 
12
13
  _LOG = logging.getLogger(__name__)
@@ -20,9 +21,8 @@ class FileHandler(FileGroups):
20
21
  Re-link symlinks when a file being deleted has a corresponding file.
21
22
 
22
23
  Arguments:
23
- protect_dirs_seq, work_dirs_seq, protect_exclude, work_include: See `FileGroups` class.
24
- dry_run: Don't actually do anything.
25
- protected_regexes: Protect files matching this from being deleted or moved.
24
+ protect_dirs_seq, work_dirs_seq, protect_exclude, work_include, config_files: See `FileGroups` class.
25
+ dry_run: Don't change any files.
26
26
  delete_symlinks_instead_of_relinking: Normal operation is to re-link to a 'corresponding' or renamed file when renaming or deleting a file.
27
27
  If delete_symlinks_instead_of_relinking is true, then symlinks in work_on dirs pointing to renamed/deletes files will be deleted even if
28
28
  they could have logically been made to point to a file in a protect dir.
@@ -32,14 +32,14 @@ class FileHandler(FileGroups):
32
32
  self,
33
33
  protect_dirs_seq: Sequence[Path], work_dirs_seq: Sequence[Path],
34
34
  *,
35
- dry_run: bool,
36
- protected_regexes: Sequence[re.Pattern],
37
35
  protect_exclude: re.Pattern|None = None, work_include: re.Pattern|None = None,
36
+ config_files: ConfigFiles|None = None,
37
+ dry_run: bool,
38
38
  delete_symlinks_instead_of_relinking=False):
39
39
  super().__init__(
40
- protect=protected_regexes,
41
40
  protect_dirs_seq=protect_dirs_seq, work_dirs_seq=work_dirs_seq,
42
- protect_exclude=protect_exclude, work_include=work_include)
41
+ protect_exclude=protect_exclude, work_include=work_include,
42
+ config_files=config_files)
43
43
 
44
44
  self.dry_run = dry_run
45
45
  self.delete_symlinks_instead_of_relinking = delete_symlinks_instead_of_relinking
@@ -7,6 +7,7 @@ from typing import Sequence
7
7
  from .compare_files import CompareFiles
8
8
  from .types import FsPath
9
9
  from .handler import FileHandler
10
+ from .config_files import ConfigFiles
10
11
 
11
12
 
12
13
  _LOG = logging.getLogger(__name__)
@@ -16,7 +17,7 @@ class FileHandlerCompare(FileHandler):
16
17
  """Extend `FileHandler` with a compare method
17
18
 
18
19
  Arguments:
19
- protect_dirs_seq, work_dirs_seq, protect_exclude, work_include: See `FileGroups` class.
20
+ protect_dirs_seq, work_dirs_seq, protect_exclude, work_include, config_files: See `FileGroups` class.
20
21
  dry_run, protected_regexes, delete_symlinks_instead_of_relinking: See `FileHandler` class.
21
22
  fcmp: Object providing compare function.
22
23
  """
@@ -25,17 +26,15 @@ class FileHandlerCompare(FileHandler):
25
26
  self,
26
27
  protect_dirs_seq: Sequence[Path], work_dirs_seq: Sequence[Path], fcmp: CompareFiles,
27
28
  *,
28
- dry_run: bool,
29
- protected_regexes: Sequence[re.Pattern],
30
29
  protect_exclude: re.Pattern|None = None, work_include: re.Pattern|None = None,
30
+ config_files: ConfigFiles|None = None,
31
+ dry_run: bool,
31
32
  delete_symlinks_instead_of_relinking=False):
32
33
  super().__init__(
33
- protect_dirs_seq=protect_dirs_seq,
34
- work_dirs_seq=work_dirs_seq,
34
+ protect_dirs_seq=protect_dirs_seq, work_dirs_seq=work_dirs_seq,
35
+ protect_exclude=protect_exclude, work_include=work_include,
36
+ config_files=config_files,
35
37
  dry_run=dry_run,
36
- protected_regexes=protected_regexes,
37
- protect_exclude=protect_exclude,
38
- work_include=work_include,
39
38
  delete_symlinks_instead_of_relinking=delete_symlinks_instead_of_relinking)
40
39
 
41
40
  self._fcmp = fcmp
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: file-groups
3
- Version: 0.2.1
3
+ Version: 0.3.0
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
@@ -0,0 +1,14 @@
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.41.3)
2
+ Generator: bdist_wheel (0.42.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -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=IDwhznEM8wpMBp0Fl69r1CbPxfgYCu82iE3b_mxkk6g,10211
4
- file_groups/groups.py,sha256=6tVIYBC5nCVPI4Ig1EuijT12accw2DZ08TEIMSJecLo,11531
5
- file_groups/handler.py,sha256=06_8_fBEaHndrfUdxMsFw6KZw8PRcRj4MsH4FY3I4sU,10083
6
- file_groups/handler_compare.py,sha256=78TXl9QFMIL5gZQhiLYvbzF5OidS7kcaRz6zCCNVG6I,2120
7
- file_groups/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- file_groups/types.py,sha256=giq3H58RuKpS6b7sjxnzP-sN1Xr4VTQ-rG4brLP5qMI,107
9
- file_groups-0.2.1.dist-info/LICENSE.txt,sha256=NZc9ictLEwfoL4ywpAbLbd-n02KETGHLYQU820TJD_Q,1494
10
- file_groups-0.2.1.dist-info/METADATA,sha256=hnoCAPIfqQ_bjodGN2qVzLhGWCBdg1-PCmYpf5G0I1U,1512
11
- file_groups-0.2.1.dist-info/WHEEL,sha256=Xo9-1PvkuimrydujYJAjF7pCkriuXBpUPEjma1nZyJ0,92
12
- file_groups-0.2.1.dist-info/top_level.txt,sha256=mAdRf0R2TA8tpH7ftHzXP7VMRhw07p7B2mI8QKpDnjU,12
13
- file_groups-0.2.1.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
14
- file_groups-0.2.1.dist-info/RECORD,,