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
         
     |