def __init__(self, pizza_cutter_path_conf_file: pathlib.Path = pathlib.Path(__file__).parent.resolve(), pizza_cutter_path_template_dir: Optional[pathlib.Path] = None, pizza_cutter_path_target_dir: Optional[pathlib.Path] = None): super().__init__(pizza_cutter_path_conf_file, pizza_cutter_path_template_dir, pizza_cutter_path_target_dir) # ############################################################################################################################################################## # PizzaCutterConfiguration # ############################################################################################################################################################## self.pizza_cutter_allow_overwrite = False self.pizza_cutter_allow_outside_write = False self.pizza_cutter_allow_outside_read = False # redefine for doctest self.pizza_cutter_options['delete_line_if_empty'] = '{{TestPizzaCutter.option.delete_line_if_empty}}' # the line will be deleted if empty self.pizza_cutter_options['object_no_copy'] = '{{TestPizzaCutter.option.no_copy}}' self.pizza_cutter_options['object_no_overwrite'] = '{{TestPizzaCutter.option.no_overwrite}}' # redefine for doctest self.pizzacutter_pattern_prefixes = ['{{PizzaCutter.', '{{cookiecutter.', '{{TestPizzaCutter'] # ############################################################################################################################################################## # Project Configuration - single point for all configuration of the project # ############################################################################################################################################################## # the name of the project, for instance for the travis repo slug path_test_dir = pathlib.Path(__file__).parent.parent.resolve() outside_target_dir = path_test_dir / 'outside_target_dir' self.project_dir = 'pizzacutter_test_project' self.pizza_cutter_patterns['{{TestPizzaCutter.project_dir}}'] = self.project_dir self.pizza_cutter_patterns['{{TestPizzaCutter.doctest}}'] = 'doctest' self.pizza_cutter_patterns['{{TestPizzaCutter.outside_target_dir}}'] = outside_target_dir
def test_interaction_with_original_pathlib() -> None: pathlib_original_file = pathlib_original.Path( 'some_path/some_path/some_path.txt') pathlib3x_file = pathlib.Path('some_path3x/some_path3x/some_path3x.txt') assert pathlib.Path.is_path_instance(pathlib_original_file) assert pathlib.Path.is_path_instance(pathlib3x_file) test_conversion = pathlib.Path(pathlib_original_file) isinstance(test_conversion, pathlib.Path)
def test_append_suffix_ok(self) -> None: # setup path_without_suffix = pathlib.Path('path_without_suffix') path_with_suffix = pathlib.Path('path_with.suffix') path_without_suffix_appended = path_without_suffix.append_suffix( '.test') path_with_suffix_appended = path_with_suffix.append_suffix('.test') # tests self.assertEqual(path_without_suffix_appended.suffix, '.test') self.assertEqual(path_without_suffix_appended.suffixes, ['.test']) self.assertEqual(path_with_suffix_appended.suffix, '.test') self.assertEqual(path_with_suffix_appended.suffixes, ['.suffix', '.test']) # test empty suffix self.assertEqual( pathlib.Path('some_path').append_suffix(''), pathlib.Path('some_path'))
def test_append_suffix_sep_or_altsep_in_suffix_raises(self) -> None: # Setup path_test = pathlib.Path('path_test') suffix_with_sep = '.test' + os.path.sep + 'test' # Test self.assertRaises(ValueError, path_test.append_suffix, suffix_with_sep) if os.path.altsep: # altsep is '/' on Windows suffix_with_altsep = '.test' + os.path.altsep + 'test' self.assertRaises(ValueError, path_test.append_suffix, suffix_with_altsep)
def build(conf_file: str, template_dir: str = '', target_dir: str = '', dry_run: bool = False, overwrite: bool = False, write_outside: bool = False) -> None: """ Builds the Project from the Template >>> # Setup >>> path_test_dir = pathlib.Path(__file__).parent.parent.resolve() / 'tests' >>> path_template_dir = path_test_dir / 'pizzacutter_test_template_01' >>> path_target_dir = path_test_dir / 'pizzacutter_test_project_01_result' >>> path_conf_file = path_template_dir / 'PizzaCutterTestConfig_01.py' >>> # Test only pass "conf_file", dry run >>> build(conf_file=str(path_conf_file), template_dir='', target_dir='', dry_run=True) >>> # Test pass "conf_file", "template_dir" and "target_dir" dry run >>> build(conf_file=str(path_conf_file), template_dir=str(path_template_dir), target_dir=str(path_target_dir), dry_run=True) """ path_conf_file = pathlib.Path(conf_file).resolve() if template_dir: path_template_dir = pathlib.Path(template_dir).resolve() else: path_template_dir = path_conf_file.parent if target_dir: path_target_dir = pathlib.Path(target_dir).resolve() else: path_target_dir = pathlib.Path.cwd().resolve() pizzacutter.build(path_conf_file=path_conf_file, path_template_dir=path_template_dir, path_target_dir=path_target_dir, dry_run=dry_run, allow_overwrite=overwrite, allow_outside_write=write_outside)
def test_shutil_wrappers() -> None: """ test the shutil wrappers """ path_test_dir = pathlib.Path(__file__).parent.resolve() path_test_file = path_test_dir / 'test.txt' path_target_file = path_test_dir / 'test_target.txt' path_test_tree = path_test_dir / 'test_treecopy' path_test_tree_target = path_test_dir / 'test_treecopy_target' path_test_file.copy(path_target_file) path_test_file.copy2(path_target_file) path_test_file.copyfile(path_target_file) path_test_file.copymode(path_target_file) path_test_file.copystat(path_target_file) path_test_tree.copytree(path_test_tree_target) path_test_tree_target.rmtree() path_target_file.unlink()
def path_remove_cutter_option_patterns( self, path_source_file: pathlib.Path) -> pathlib.Path: """ removes option patterns from the filename - those are already checked and considered earlier >>> # Setup >>> path_test_dir = pathlib.Path(__file__).parent.parent / 'tests' >>> path_template_dir = path_test_dir / 'pizzacutter_test_template_01' >>> path_conf_file = path_template_dir / 'PizzaCutterTestConfig_01.py' >>> path_target_dir = path_test_dir / 'pizzacutter_test_project_01' >>> pizza_cutter = PizzaCutter(path_conf_file, path_template_dir, path_target_dir) >>> # test ok >>> test_file = path_template_dir/ 'test.txt{{TestPizzaCutter.option.no_copy}}' >>> pizza_cutter.path_remove_cutter_option_patterns(test_file) <BLANKLINE> ...Path('.../tests/pizzacutter_test_template_01/test.txt') >>> # directory only option patterns Fails >>> test_file = path_template_dir/ '{{TestPizzaCutter.option.no_copy}}/test.txt{{TestPizzaCutter.option.no_copy}}' >>> pizza_cutter.path_remove_cutter_option_patterns(test_file) Traceback (most recent call last): ... RuntimeError: No part of the path ... >>> # File only option patterns Fails >>> test_file = path_template_dir/ '{{TestPizzaCutter.option.no_copy}}.test/{{TestPizzaCutter.option.no_copy}}' >>> pizza_cutter.path_remove_cutter_option_patterns(test_file) Traceback (most recent call last): ... RuntimeError: No part of the path ... """ source_file_parts = path_source_file.parts result_file_parts = list() for source_file_part in source_file_parts: for option_pattern in self.conf.pizza_cutter_options.values(): source_file_part = source_file_part.replace(option_pattern, '') if not source_file_part: raise RuntimeError( f'No part of the path must consist ONLY of option patterns: "{path_source_file}"' ) result_file_parts.append(source_file_part) result_path_source_file = pathlib.Path(*result_file_parts) return result_path_source_file
def path_replace_string_patterns( self, path_source_object_resolved: pathlib.Path) -> pathlib.Path: """ replaces string patterns in the filename >>> # Setup >>> path_test_dir = pathlib.Path(__file__).parent.parent / 'tests' >>> path_template_dir = path_test_dir / 'pizzacutter_test_template_01' >>> path_conf_file = path_template_dir / 'PizzaCutterTestConfig_01.py' >>> path_target_dir = path_test_dir / 'pizzacutter_test_project_01' >>> pizza_cutter = PizzaCutter(path_conf_file, path_template_dir, path_target_dir) >>> # test no replacements >>> test_file = path_template_dir/ 'test.txt' >>> pizza_cutter.path_replace_string_patterns(test_file) <BLANKLINE> ...Path('.../tests/pizzacutter_test_template_01/test.txt') >>> # test with replacement >>> pizza_cutter.conf.pizza_cutter_patterns['{{TestPizzaCutter.doctest}}'] = 'doctest' >>> test_file = path_template_dir/ 'test_{{TestPizzaCutter.doctest}}.txt' >>> pizza_cutter.path_replace_string_patterns(test_file) <BLANKLINE> ...Path('.../tests/pizzacutter_test_template_01/test_doctest.txt') """ source_file_parts = path_source_object_resolved.parts result_file_parts = list() for source_file_part in source_file_parts: for pattern in self.conf.pizza_cutter_patterns.keys(): replacement = self.conf.pizza_cutter_patterns[pattern] if isinstance(replacement, str): source_file_part = source_file_part.replace( pattern, replacement) result_file_parts.append(source_file_part) result_path_source_file = pathlib.Path(*result_file_parts) return result_path_source_file
def pizza_cutter_instance(): path_test_dir = pathlib.Path(__file__).parent.parent.resolve() / 'tests' path_template_dir = path_test_dir / 'pizzacutter_test_template_02' path_conf_file = path_template_dir / 'PizzaCutterTestConfig_02.py' path_target_dir = path_test_dir / 'test_target' path_outside_target_dir = get_outside_target_dir() pizza_cutter = pizzacutter.PizzaCutter(path_conf_file=path_conf_file, path_template_dir=path_template_dir, path_target_dir=path_target_dir) yield pizza_cutter # provide the fixture value # teardown code if not path_target_dir.is_relative_to(path_test_dir): raise RuntimeError( 'attempt to delete "{}" which is outside the test dir "{}"'.format( pizza_cutter_instance.path_target_dir, path_test_dir)) if not path_target_dir.is_relative_to(path_test_dir): raise RuntimeError( 'attempt to delete "{}" which is outside the test dir "{}"'.format( path_outside_target_dir, path_test_dir)) shutil.rmtree(path_target_dir, ignore_errors=True) shutil.rmtree(path_outside_target_dir, ignore_errors=True)
def __init__( self, # the path to the PizzaCutter conf File path_conf_file: pathlib.Path, # the path to the Template Folder - can be set by the conf File to the Directory the conf file sits - can be overridden by untrusted conf_file path_template_dir: Optional[pathlib.Path] = None, # the target path of the Project Folder - this should be the current Directory - can be overridden by conf_file path_target_dir: Optional[pathlib.Path] = None, # dry run - test only, report overwrites, files outside project directory, unset patterns, unused patterns from conf file # only made the easy tests now - for full test of replacements we would need to install into a temp directory dry_run: Optional[bool] = None, # allow overwrite in the target Project, can be overridden by conf_file allow_overwrite: Optional[bool] = None, # allow to write files outside of the target Project Folder, can be overridden by conf_file allow_outside_write: Optional[bool] = None, quiet: Optional[bool] = None): """ Init reads the config file and sets up the neccessary class properties >>> # Setup >>> path_test_dir = pathlib.Path(__file__).parent.parent.resolve() / 'tests' >>> path_template_dir = path_test_dir / 'pizzacutter_test_template_01' >>> path_conf_file = path_template_dir / 'PizzaCutterTestConfig_01.py' >>> # Test init, only conf file passed, quiet >>> pizza_cutter = PizzaCutter(path_conf_file=path_conf_file, quiet=True) >>> # Test init, conf file not found >>> pizza_cutter = PizzaCutter(path_conf_file=pathlib.Path(), quiet=True) Traceback (most recent call last): ... FileNotFoundError: the config file ... can not be found """ if not path_conf_file.is_file(): raise FileNotFoundError( f'the config file "{path_conf_file}" can not be found') self.conf = get_config.PizzaCutterGetConfig( pizza_cutter_path_conf_file=path_conf_file, pizza_cutter_path_template_dir=path_template_dir, pizza_cutter_path_target_dir=path_target_dir).conf if path_template_dir is None: # we call again pathlib.Path, to be sure it is pathlib3x Type self.path_template_dir = pathlib.Path( self.conf.pizza_cutter_path_template_dir) else: self.path_template_dir = pathlib.Path(path_template_dir) if path_target_dir is None: self.path_target_dir = pathlib.Path( self.conf.pizza_cutter_path_target_dir) else: self.path_target_dir = pathlib.Path(path_target_dir) if allow_overwrite is None: self.allow_overwrite = self.conf.pizza_cutter_allow_overwrite else: self.allow_overwrite = allow_overwrite if allow_outside_write is None: self.allow_outside_write = self.conf.pizza_cutter_allow_outside_write else: self.allow_outside_write = allow_outside_write if dry_run is None: self.dry_run = self.conf.pizza_cutter_dry_run else: self.dry_run = dry_run if quiet is None: self.quiet = self.conf.pizza_cutter_quiet else: self.quiet = quiet self.file_stack: List[pathlib.Path] = list() self.pattern_stack: List[str] = list()
def import_module_from_file(module_fullpath: Union[pathlib.Path, str], reload: bool = False): # type: ignore """ TODO : replace with lib_import when avail maybe take from pycharm, there we do the full coverage ... >>> # re-import from file >>> import_module_from_file(pathlib.Path(__file__)) <module 'import_module' from '...import_module.py'> >>> # re-import from file without extension >>> import_module_from_file(pathlib.Path(__file__).with_suffix('')) <module 'import_module' from '...import_module.py'> >>> # re-import from file, reload = True >>> import_module_from_file(pathlib.Path(__file__), reload=True) <module 'import_module' from '...import_module.py'> >>> # import from non-existing file, and invalid module name. reload = True >>> # re-import from non-existing file, but already imported module name. reload = True >>> import_module_from_file(pathlib.Path(__file__).with_suffix('.non_existing'), reload=True) Traceback (most recent call last): ... FileNotFoundError: module "...import_module.non_existing.py" not found """ module_fullpath = pathlib.Path(module_fullpath) if not module_fullpath.suffix == '.py': module_fullpath = pathlib.Path(str(module_fullpath) + '.py') if not module_fullpath.is_file(): raise FileNotFoundError('module "{}" not found'.format(module_fullpath)) module_name = module_fullpath.stem if not reload and module_name in sys.modules: return sys.modules[module_name] if reload: # see https://docs.python.org/3/library/importlib.html importlib.invalidate_caches() sys.path.append(str(module_fullpath.parent)) spec = importlib.util.spec_from_file_location(module_name, module_fullpath) if spec is None: sys.path.pop() raise ImportError('can not get spec from file location "{}"'.format(module_fullpath)) try: mod = importlib.util.module_from_spec(spec) sys.modules[module_name] = mod except Exception as exc: raise ImportError('can not load module "{}"'.format(module_name)) from exc finally: sys.path.pop() sys.path.append(str(module_fullpath.parent)) try: spec.loader.exec_module(mod) # type: ignore except Exception as exc: sys.path.pop() raise ImportWarning('module "{}" reloaded, but can not be executed'.format(module_name)) from exc return mod
def test_unlink_missing_ok(self) -> None: pathlib.Path('__not_existing__').unlink(missing_ok=True)
def test_append_suffix_invalid_suffix_raises(self) -> None: path_test = pathlib.Path('some_path') # test suffix not starting with '.' self.assertRaises(ValueError, path_test.append_suffix, 'test') # test suffix is only '.' self.assertRaises(ValueError, path_test.append_suffix, '.')
def test_glob(self) -> None: pathlib.Path('.').glob('**/*')
def get_outside_target_dir() -> pathlib.Path: path_test_dir = pathlib.Path(__file__).parent.parent.resolve() / 'tests' outside_target_dir = path_test_dir / 'outside_target_dir' return outside_target_dir
def __init__(self, # the path to the actual config File pizza_cutter_path_conf_file: Optional[pathlib.Path] = None, # the default for the template Folder is the actual directory of the given config File pizza_cutter_path_template_dir: Optional[pathlib.Path] = None, # the project Folder is the current directory pizza_cutter_path_target_dir: Optional[pathlib.Path] = None): """ The Base Class for the Pizza Cutter Configuration >>> # Test Config File not found >>> config = PizzaCutterConfigBase(pizza_cutter_path_conf_file=pathlib.Path('not_existing_conf_file')) Traceback (most recent call last): ... FileNotFoundError: PizzaCutter config file "not_existing_conf_file" does not exist >>> # Test Config File not found >>> config = PizzaCutterConfigBase(pizza_cutter_path_template_dir=pathlib.Path('not_existing_template_directory')) Traceback (most recent call last): ... NotADirectoryError: Template Directory "not_existing_template_directory" must be an existing Directory """ if pizza_cutter_path_conf_file is None: pizza_cutter_path_conf_file = pathlib.Path(__file__).resolve() else: if not pizza_cutter_path_conf_file.is_file(): raise FileNotFoundError('PizzaCutter config file "{}" does not exist'.format(pizza_cutter_path_conf_file)) # make sure it is a pathlib3x object pizza_cutter_path_conf_file = pathlib.Path(pizza_cutter_path_conf_file) pizza_cutter_path_conf_file = pizza_cutter_path_conf_file.resolve() if pizza_cutter_path_template_dir is None: pizza_cutter_path_template_dir = pizza_cutter_path_conf_file.resolve().parent else: if not pizza_cutter_path_template_dir.is_dir(): raise NotADirectoryError('Template Directory "{}" must be an existing Directory'.format(pizza_cutter_path_template_dir)) # make sure it is a pathlib3x object pizza_cutter_path_template_dir = pathlib.Path(pizza_cutter_path_template_dir) pizza_cutter_path_template_dir = pizza_cutter_path_template_dir.resolve() if pizza_cutter_path_target_dir is None: pizza_cutter_path_target_dir = pathlib.Path.cwd().resolve() else: # make sure it is a pathlib3x object pizza_cutter_path_target_dir = pathlib.Path(pizza_cutter_path_target_dir) self.pizza_cutter_path_conf_file = pizza_cutter_path_conf_file self.pizza_cutter_path_template_dir = pizza_cutter_path_template_dir self.pizza_cutter_path_target_dir = pizza_cutter_path_target_dir self.pizza_cutter_options: Dict[str, str] = dict() # the settings from the CLI can only be overwritten by configuration files self.pizza_cutter_allow_overwrite = False self.pizza_cutter_allow_outside_write = False self.pizza_cutter_dry_run = False self.pizza_cutter_quiet = False # for patterns to look out after all replacements, in order to find unfilled patterns self.pizzacutter_pattern_prefixes = ['{{PizzaCutter', '{{cookiecutter', '{{pizzacutter', '{{Pizzacutter'] # ###################################################################################################################################################### # replacement patterns # ###################################################################################################################################################### # replacement patterns can be string, or pathlib.Path Objects - the pathlib Objects can be absolute or relative # if You chain such pathlib Objects in template files or directories,, the final destination of the file might be not were You expected. # since You might pass relative or absolute paths to the PizzaCutter CLI Application, You should be careful # about the resulting paths, especially if You pass absolute paths. # beware of differences in Linux and Windows : on Windows pathlib.Path('/test') is relative, on Linux it is absolute ! # best practice is to use relative paths in the form pathlib.Path('./test') # with great flexibility there comes big responsibility. You should test Your Pizzacutter conf_files with absolute and relative Paths # for the project path, and check carefully the result. We might disallow absolute paths in the future, or only enable it with a flag, # not to allow dangerous Pizzacutter conf_files to overwrite system files. # in general, if not really needed on purpose, we would suggest to use only string replacements in directory- and filenames. # on the other hand, this feature can be very useful, in order t drop files to the user desktop, # user home, windows appdir, etc... OUTSIDE of the Project Path given # path replacement patterns are also valid in text files # in that case the pattern will be replaced with the content of that file (if found) # if the file is not found, or not readable, the string of the path will be filled in. (with a warning) # You can even include Files from outside the template Folder, or from the Project Folder itself. # Those replacements will be done AFTER the template Files are copied to the target Project, to make sure that even # replacements from the target project file work properly. # this can be useful for situations like: # /template_folder/my_special_configuration{{PizzaCutter.option.no_overwrite}}.txt # template for the special configuration # /template_folder/some_file.txt # that file includes /project_folder/my_special_configuration.txt # in that case, /project_folder/some_file.txt will include /project_folder/my_special_configuration.txt correctly, # even if the project is just created. # chaining of only relative Paths : # {{PizzaCutter.relative_path_object1}} = pathlib.Path('test1/test2') # relative path # {{PizzaCutter.relative_path_object2}} = pathlib.Path('test3/test4') # relative path # .../template_directory/{{PizzaCutter.relative_path_object1}}/{{PizzaCutter.relative_path_object2}}/test.txt will work as expected. and resolve to: # .../template_directory/test1/test2/test3/test4/test.txt --> .../project_directory/test1/test2/test3/test4/test.txt # chaining of Absolute and Relative Paths : # {{PizzaCutter.absolute_path_object1}} = pathlib.Path('/test1/test2') # absolute Path # {{PizzaCutter.relative_path_object2}} = pathlib.Path('test3/test4') # relative path # .../template_directory/{{PizzaCutter.absolute_path_object1}}/{{PizzaCutter.relative_path_object2}}/test.txt will work as expected. and resolve to: # /test1/test2/test3/test4/test.txt # by that way You might even write configuration files into /usr/etc or similar (depending on Your rights)! # unexpected Result when chaining Absolute and Relative Paths in the wrong order : # {{PizzaCutter.relative_path_object1}} = pathlib.Path('test1/test2') # relative Path # {{PizzaCutter.absolute_path_object2}} = pathlib.Path('/test3/test4') # absolute path # .../template_directory/{{PizzaCutter.relative_path_object1}}/{{PizzaCutter.absolute_path_object2}}/test.txt will work unexpected and resolve to: # /test3/test4/test.txt # by that way You might even write configuration files into /usr/etc or similar (depending on Your rights)! # ###################################################################################################################################################### self.pizza_cutter_patterns: Dict[str, Union[str, pathlib.Path]] = dict() # this is useful in scripts, to detect if cutting already happened # for instance bash: if [[ "{{PizzaCutter.True}}" == "True" ]]; then ... self.pizza_cutter_patterns['{{PizzaCutter.True}}'] = 'True' # ###################################################################################################################################################### # cutter_options # ###################################################################################################################################################### # You might name Your Patterns as You like, for instance CakeCutter, LemonCutter, MelonCutter whatever ;-) # even the Names for the Option Flags can be configured here # You must not change the keys for that options, those are hardcoded self.pizza_cutter_options['delete_line_if_empty'] = '{{PizzaCutter.option.delete_line_if_empty}}' # the line will be deleted if empty # files or directories with that marker that will not be copied to target # files within a directory with that marker will not be copied to target, so You dont have to mark each file seperately self.pizza_cutter_options['object_no_copy'] = '{{PizzaCutter.option.no_copy}}' # files or directories with that marker that will not overwritten on Target # files within a directory with that marker will not be overwritten on the target, so You dont have to mark each file seperately self.pizza_cutter_options['object_no_overwrite'] = '{{PizzaCutter.option.no_overwrite}}'
def path_replace_pathlib_patterns( self, path_source_path: pathlib.Path) -> pathlib.Path: """ Returns the resolved Target Path >>> # Setup >>> path_test_dir = pathlib.Path(__file__).parent.parent / 'tests' >>> path_template_dir = path_test_dir / 'pizzacutter_test_template_01' >>> path_conf_file = path_template_dir / 'PizzaCutterTestConfig_01.py' >>> path_target_dir = path_test_dir / 'pizzacutter_test_project_01' >>> pizza_cutter = PizzaCutter(path_conf_file, path_template_dir, path_target_dir) >>> # test absolute replacement + relative replacement >>> import platform >>> if platform.system().lower() == 'windows': ... pizza_cutter.conf.pizza_cutter_patterns['{{TestPizzaCutter.path.doctest.absolute}}'] = pathlib.Path('c:/test/doctest_absolute') ... else: ... pizza_cutter.conf.pizza_cutter_patterns['{{TestPizzaCutter.path.doctest.absolute}}'] = pathlib.Path('/test/doctest_absolute') >>> pizza_cutter.conf.pizza_cutter_patterns['{{TestPizzaCutter.path.doctest.relative}}'] = pathlib.Path('./doctest') >>> test_file = path_template_dir/ '{{TestPizzaCutter.path.doctest.absolute}}/{{TestPizzaCutter.path.doctest.relative}}/test.txt' >>> pizza_cutter.path_replace_pathlib_patterns(test_file) <BLANKLINE> ...Path('.../doctest_absolute/doctest/test.txt') >>> # test no replacements >>> test_file = path_template_dir/ 'test.txt' >>> pizza_cutter.path_replace_pathlib_patterns(test_file) <BLANKLINE> ...Path('.../tests/pizzacutter_test_project_01/test.txt') >>> # test relative replacements >>> pizza_cutter.conf.pizza_cutter_patterns['{{TestPizzaCutter.path.doctest.relative}}'] = pathlib.Path('./doctest') >>> test_file = path_template_dir/ '{{TestPizzaCutter.path.doctest.relative}}/{{TestPizzaCutter.path.doctest.relative}}/test.txt' >>> pizza_cutter.path_replace_pathlib_patterns(test_file) <BLANKLINE> ...Path('.../tests/pizzacutter_test_project_01/doctest/doctest/test.txt') >>> # test relative replacement + absolute replacement >>> if platform.system().lower() == 'windows': ... pizza_cutter.conf.pizza_cutter_patterns['{{TestPizzaCutter.path.doctest.absolute}}'] = pathlib.Path('c:/test/doctest_absolute') ... else: ... pizza_cutter.conf.pizza_cutter_patterns['{{TestPizzaCutter.path.doctest.absolute}}'] = pathlib.Path('/test/doctest_absolute') >>> pizza_cutter.conf.pizza_cutter_patterns['{{TestPizzaCutter.path.doctest.relative}}'] = pathlib.Path('./doctest') >>> test_file = path_template_dir/ '{{TestPizzaCutter.path.doctest.relative}}/{{TestPizzaCutter.path.doctest.absolute}}/test.txt' >>> pizza_cutter.path_replace_pathlib_patterns(test_file) <BLANKLINE> ...Path('.../doctest_absolute/test.txt') >>> # test absolute replacement + absolute replacement (last "wins") >>> if platform.system().lower() == 'windows': ... pizza_cutter.conf.pizza_cutter_patterns['{{TestPizzaCutter.path.doctest.absolute1}}'] = pathlib.Path('c:/test/doctest_absolute1') ... else: ... pizza_cutter.conf.pizza_cutter_patterns['{{TestPizzaCutter.path.doctest.absolute1}}'] = pathlib.Path('/test/doctest_absolute1') >>> if platform.system().lower() == 'windows': ... pizza_cutter.conf.pizza_cutter_patterns['{{TestPizzaCutter.path.doctest.absolute2}}'] = pathlib.Path('c:/test/doctest_absolute2') ... else: ... pizza_cutter.conf.pizza_cutter_patterns['{{TestPizzaCutter.path.doctest.absolute2}}'] = pathlib.Path('/test/doctest_absolute2') >>> test_file = path_template_dir/ '{{TestPizzaCutter.path.doctest.absolute1}}/{{TestPizzaCutter.path.doctest.absolute2}}/test.txt' >>> pizza_cutter.path_replace_pathlib_patterns(test_file) <BLANKLINE> ...Path('.../doctest_absolute2/test.txt') >>> # test path replacement not complete part of a path (name is also a complete part !!!) >>> pizza_cutter.conf.pizza_cutter_patterns['{{TestPizzaCutter.path.doctest.relative}}'] = pathlib.Path('./doctest') >>> test_file = path_template_dir/ '{{TestPizzaCutter.path.doctest.relative}}/{{TestPizzaCutter.path.doctest.relative}}.txt' >>> pizza_cutter.path_replace_pathlib_patterns(test_file) Traceback (most recent call last): ... RuntimeError: ... can only be one complete part of a path : ...{{...doctest.relative}}.txt", Pattern: {{TestPizzaCutter.path.doctest.relative}} """ source_object_parts = reversed(path_source_path.parts) target_parts = list() absolute_path_found = False for source_object_part in source_object_parts: target_object_part: Union[str, pathlib.Path] = source_object_part for pattern in self.conf.pizza_cutter_patterns.keys(): replacement = self.conf.pizza_cutter_patterns[pattern] # we need this, because pathlib3x.Path is NOT instance of pathlib.Path, # but the User might use pathlib in his config File ! if isinstance(replacement, str): continue if pattern in source_object_part: if source_object_part != pattern: raise RuntimeError( f'pathlib.Path patterns can only be one complete part of a path : Path: "{path_source_path}", Pattern: {pattern}' ) else: target_object_part = pathlib.Path(replacement) if target_object_part.is_absolute( ) and absolute_path_found: logger.warning( 'the resulting path might be unexpected, You have more then one absolute pathlib.Path pattern in the path: ' f'"{path_source_path}", Pattern: "{pattern}" points to "{replacement}"' ) if not absolute_path_found: target_parts.append(target_object_part) if not isinstance(target_object_part, str) and pathlib.Path( target_object_part).is_absolute(): absolute_path_found = True target_parts = list(reversed(target_parts)) path_target_path = pathlib.Path(*target_parts).resolve() if absolute_path_found: if not self.quiet: logger.warning( 'the resulting path of a template file might be unexpected, You have an absolute pathlib.Path pattern in the path: ' f'"{path_source_path}" points to "{path_target_path}"') else: path_target_path = path_target_path.replace_parts( self.path_template_dir.resolve(), self.path_target_dir.resolve()) return path_target_path
def test_append_suffix_empty_name_raises(self) -> None: path_with_empty_name = pathlib.Path('') self.assertRaises(ValueError, path_with_empty_name.append_suffix, '.test')