diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1c364fe..735cacd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,10 +8,11 @@ on: jobs: test: - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} strategy: matrix: python-version: ['3.13', '3.12', '3.11', '3.10', '3.9'] + os: [ubuntu-latest, windows-latest] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} diff --git a/gitignore_parser.py b/gitignore_parser.py index ef91b2b..5ccb003 100644 --- a/gitignore_parser.py +++ b/gitignore_parser.py @@ -4,6 +4,7 @@ from os.path import abspath, dirname, join from pathlib import Path +import sys from typing import Reversible, Union def handle_negation(file_path, rules: Reversible["IgnoreRule"]): @@ -129,9 +130,14 @@ def __repr__(self): def match(self, abs_path: Union[str, Path]): matched = False if self.base_path: - rel_path = str(_normalize_path(abs_path).relative_to(self.base_path)) + rel_path = _normalize_path(abs_path).relative_to(self.base_path).as_posix() else: - rel_path = str(_normalize_path(abs_path)) + rel_path = _normalize_path(abs_path).as_posix() + # Path() strips the trailing following symbols on windows, so we need to + # preserve it: ' ', '.' + if sys.platform.startswith('win'): + rel_path += ' ' * _count_trailing_symbol(' ', abs_path) + rel_path += '.' * _count_trailing_symbol('.', abs_path) # Path() strips the trailing slash, so we need to preserve it # in case of directory-only negation if self.negation and type(abs_path) == str and abs_path[-1] == '/': @@ -222,3 +228,14 @@ def _normalize_path(path: Union[str, Path]) -> Path: `Path.resolve()` does. """ return Path(abspath(path)) + + +def _count_trailing_symbol(symbol: str, text: str) -> int: + """Count the number of trailing characters in a string.""" + count = 0 + for char in reversed(str(text)): + if char == symbol: + count += 1 + else: + break + return count diff --git a/tests.py b/tests.py index bd63855..650febd 100644 --- a/tests.py +++ b/tests.py @@ -4,7 +4,7 @@ from gitignore_parser import parse_gitignore, parse_gitignore_str -from unittest import TestCase, main +from unittest import TestCase, main, SkipTest class Test(TestCase): @@ -206,13 +206,20 @@ def test_slash_in_range_does_not_match_dirs(self): def test_symlink_to_another_directory(self): with TemporaryDirectory() as project_dir: + project_dir = Path(project_dir).resolve() with TemporaryDirectory() as another_dir: + another_dir = Path(another_dir).resolve() matches = parse_gitignore_str('link', base_dir=project_dir) # Create a symlink to another directory. - link = Path(project_dir, 'link') - target = Path(another_dir, 'target') - link.symlink_to(target) + link = project_dir / 'link' + target = another_dir / 'target' + try: + link.symlink_to(target) + except OSError: + raise SkipTest( + "Current user does not have permissions to perform symlink." + ) # Check the intended behavior according to # https://git-scm.com/docs/gitignore#_notes: @@ -222,13 +229,19 @@ def test_symlink_to_another_directory(self): def test_symlink_to_symlink_directory(self): with TemporaryDirectory() as project_dir: + project_dir = Path(project_dir).resolve() with TemporaryDirectory() as link_dir: - link = Path(link_dir, 'link') - link.symlink_to(project_dir) + link_dir = link_dir.resolve() + link = link_dir / 'link' + try: + link.symlink_to(project_dir) + except OSError: + raise SkipTest( + "Current user does not have permissions to perform symlink." + ) file = Path(link, 'file.txt') matches = parse_gitignore_str('file.txt', base_dir=str(link_dir)) self.assertTrue(matches(file)) - if __name__ == '__main__': main()