From bc9f0b32039325e96683e0e4a3ea84428a336c8d Mon Sep 17 00:00:00 2001 From: rsiemens Date: Sun, 27 Jan 2019 11:30:06 -0800 Subject: [PATCH] Refs #29973 -- Extracted helper functions from makemessages. --- .../core/management/commands/makemessages.py | 29 ++---------------- django/core/management/utils.py | 30 +++++++++++++++++++ tests/user_commands/tests.py | 25 +++++++++++++++- 3 files changed, 57 insertions(+), 27 deletions(-) diff --git a/django/core/management/commands/makemessages.py b/django/core/management/commands/makemessages.py index 2d0227b493..6fc2028688 100644 --- a/django/core/management/commands/makemessages.py +++ b/django/core/management/commands/makemessages.py @@ -1,4 +1,3 @@ -import fnmatch import glob import os import re @@ -12,7 +11,7 @@ from django.core.exceptions import ImproperlyConfigured from django.core.files.temp import NamedTemporaryFile from django.core.management.base import BaseCommand, CommandError from django.core.management.utils import ( - find_command, handle_extensions, popen_wrapper, + find_command, handle_extensions, is_ignored_path, popen_wrapper, ) from django.utils.encoding import DEFAULT_LOCALE_ENCODING from django.utils.functional import cached_property @@ -454,35 +453,13 @@ class Command(BaseCommand): Get all files in the given root. Also check that there is a matching locale dir for each file. """ - def is_ignored(path, ignore_patterns): - """ - Check if the given path should be ignored or not. - """ - filename = os.path.basename(path) - - def ignore(pattern): - return fnmatch.fnmatchcase(filename, pattern) or fnmatch.fnmatchcase(path, pattern) - - return any(ignore(pattern) for pattern in ignore_patterns) - - ignore_patterns = [os.path.normcase(p) for p in self.ignore_patterns] - dir_suffixes = {'%s*' % path_sep for path_sep in {'/', os.sep}} - norm_patterns = [] - for p in ignore_patterns: - for dir_suffix in dir_suffixes: - if p.endswith(dir_suffix): - norm_patterns.append(p[:-len(dir_suffix)]) - break - else: - norm_patterns.append(p) - all_files = [] ignored_roots = [] if self.settings_available: ignored_roots = [os.path.normpath(p) for p in (settings.MEDIA_ROOT, settings.STATIC_ROOT) if p] for dirpath, dirnames, filenames in os.walk(root, topdown=True, followlinks=self.symlinks): for dirname in dirnames[:]: - if (is_ignored(os.path.normpath(os.path.join(dirpath, dirname)), norm_patterns) or + if (is_ignored_path(os.path.normpath(os.path.join(dirpath, dirname)), self.ignore_patterns) or os.path.join(os.path.abspath(dirpath), dirname) in ignored_roots): dirnames.remove(dirname) if self.verbosity > 1: @@ -493,7 +470,7 @@ class Command(BaseCommand): for filename in filenames: file_path = os.path.normpath(os.path.join(dirpath, filename)) file_ext = os.path.splitext(filename)[1] - if file_ext not in self.extensions or is_ignored(file_path, self.ignore_patterns): + if file_ext not in self.extensions or is_ignored_path(file_path, self.ignore_patterns): if self.verbosity > 1: self.stdout.write('ignoring file %s in %s\n' % (filename, dirpath)) else: diff --git a/django/core/management/utils.py b/django/core/management/utils.py index d2b85c986f..c7580c1931 100644 --- a/django/core/management/utils.py +++ b/django/core/management/utils.py @@ -1,4 +1,6 @@ +import fnmatch import os +from pathlib import Path from subprocess import PIPE, Popen from django.apps import apps as installed_apps @@ -122,3 +124,31 @@ def get_command_line_option(argv, option): return None else: return options.value + + +def normalize_path_patterns(patterns): + """Normalize an iterable of glob style patterns based on OS.""" + patterns = [os.path.normcase(p) for p in patterns] + dir_suffixes = {'%s*' % path_sep for path_sep in {'/', os.sep}} + norm_patterns = [] + for pattern in patterns: + for dir_suffix in dir_suffixes: + if pattern.endswith(dir_suffix): + norm_patterns.append(pattern[:-len(dir_suffix)]) + break + else: + norm_patterns.append(pattern) + return norm_patterns + + +def is_ignored_path(path, ignore_patterns): + """ + Check if the given path should be ignored or not based on matching + one of the glob style `ignore_patterns`. + """ + path = Path(path) + + def ignore(pattern): + return fnmatch.fnmatchcase(path.name, pattern) or fnmatch.fnmatchcase(str(path), pattern) + + return any(ignore(pattern) for pattern in normalize_path_patterns(ignore_patterns)) diff --git a/tests/user_commands/tests.py b/tests/user_commands/tests.py index 45fe0aaf46..25ab82baed 100644 --- a/tests/user_commands/tests.py +++ b/tests/user_commands/tests.py @@ -8,7 +8,8 @@ from django.apps import apps from django.core import management from django.core.management import BaseCommand, CommandError, find_commands from django.core.management.utils import ( - find_command, get_random_secret_key, popen_wrapper, + find_command, get_random_secret_key, is_ignored_path, + normalize_path_patterns, popen_wrapper, ) from django.db import connection from django.test import SimpleTestCase, override_settings @@ -268,3 +269,25 @@ class UtilsTests(SimpleTestCase): self.assertEqual(len(key), 50) for char in key: self.assertIn(char, 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)') + + def test_is_ignored_path_true(self): + patterns = ( + ['foo/bar/baz'], + ['baz'], + ['foo/bar/baz'], + ['*/baz'], + ['*'], + ['b?z'], + ['[abc]az'], + ['*/ba[!z]/baz'], + ) + for ignore_patterns in patterns: + with self.subTest(ignore_patterns=ignore_patterns): + self.assertIs(is_ignored_path('foo/bar/baz', ignore_patterns=ignore_patterns), True) + + def test_is_ignored_path_false(self): + self.assertIs(is_ignored_path('foo/bar/baz', ignore_patterns=['foo/bar/bat', 'bar', 'flub/blub']), False) + + def test_normalize_path_patterns_truncates_wildcard_base(self): + expected = [os.path.normcase(p) for p in ['foo/bar', 'bar/*/']] + self.assertEqual(normalize_path_patterns(['foo/bar/*', 'bar/*/']), expected)