Skip to content

Instantly share code, notes, and snippets.

@wylee
Created December 19, 2016 23:16
Show Gist options
  • Save wylee/ad0271ca1fdc6c0f3a60c576b741580a to your computer and use it in GitHub Desktop.
Save wylee/ad0271ca1fdc6c0f3a60c576b741580a to your computer and use it in GitHub Desktop.
import fnmatch
import mimetypes
import os
from django.conf import settings
from django.core.management.base import BaseCommand
from arcutils.colorize import printer
class Command(BaseCommand):
help = 'Check for unused templates'
def add_arguments(self, parser):
parser.add_argument(
'-s', '--source', default='.',
help='Where to find templates to check for usage. '
'Defaults to the current directory.'
)
parser.add_argument(
'-w', '--where', nargs='*', default=['.'],
help='Where to look for usage of templates. '
'Multiple directories can be specified. '
'Defaults to the current directory.'
)
parser.add_argument(
'-e', '--exclude', nargs='*',
default=[
'search',
],
help='Template subdirectories to exclude. '
'Multiple subdirectories can be specified. '
'Usually, these will correspond to app names.'
)
parser.add_argument(
'--ignore-dirs', nargs='*',
default=[
'.*',
'*.egg-info',
'__pycache__',
'build',
'dist',
'node_modules',
'static',
],
help='Do not look for templates or check for their usage in these directories.'
)
parser.add_argument(
'--ignore-files', nargs='*',
default=[
'.*',
'CHANGELOG*',
'README*',
],
help='Skip these files when checking for template usage.'
)
def handle(self, *args, **options):
self.source = os.path.normpath(os.path.abspath(options['source']))
self.where = [os.path.normpath(os.path.abspath(w)) for w in options['where']]
self.exclude = options['exclude']
self.ignore_dirs = options['ignore_dirs']
self.ignore_files = options['ignore_files']
self.all_files = self.get_files_to_check(self.where)
source_dirs = self.get_source_dirs(self.source)
source_files = self.get_source_files(source_dirs)
unused = self.find_unused(source_files)
num_unused = len(unused)
if unused:
s = '' if num_unused == 1 else 's'
printer.warning('Found', num_unused, 'template{s} that MAY be unused:'.format(s=s))
for entry in unused:
full_path, short_path, base_name = entry
print(os.path.relpath(full_path, self.source))
else:
printer.success('No unused templates found')
def find_unused(self, paths):
unused = []
for entry in paths:
full_path, short_path, base_name = entry
if self.is_unused(short_path):
unused.append(entry)
return unused
def is_unused(self, short_path):
if self.is_excluded(short_path):
return False
for file_name in self.all_files:
with open(file_name) as fp:
contents = fp.read()
if short_path in contents:
return False
return True
def get_files_to_check(self, where):
files = []
for dir_ in where:
if not os.path.isdir(dir_):
raise NotADirectoryError(dir_)
sub_dirs = []
for base_name in os.listdir(dir_):
full_path = os.path.join(dir_, base_name)
if os.path.isfile(full_path):
if not self.is_ignored_file(full_path):
files.append(full_path)
elif os.path.isdir(full_path):
if not self.is_ignored_dir(full_path):
sub_dirs.append(full_path)
if sub_dirs:
files.extend(self.get_files_to_check(sub_dirs))
return files
def get_source_dirs(self, where):
dirs = []
for base_name in os.listdir(where):
full_path = os.path.join(where, base_name)
skip = (
not os.path.isdir(full_path) or
self.is_ignored_dir(full_path)
)
if not skip:
if base_name == 'templates':
dirs.append(full_path)
else:
dirs.extend(self.get_source_dirs(full_path))
return dirs
def get_source_files(self, template_dirs):
files = []
for dir_ in template_dirs:
for base_name in os.listdir(dir_):
full_path = os.path.join(dir_, base_name)
if os.path.isdir(full_path):
files.extend(self.get_source_files([full_path]))
elif os.path.isfile(full_path):
short_path = os.path.relpath(full_path, os.path.dirname(dir_))
d, *rest = os.path.split(short_path)
if d == 'templates':
short_path = os.path.join(*rest)
files.append((full_path, short_path, base_name))
return files
def is_ignored_dir(self, full_path):
if full_path in (settings.MEDIA_ROOT, settings.STATIC_ROOT):
return True
for dir_ in self.where:
if full_path == os.path.join(dir_, 'media'):
return True
base_name = os.path.basename(full_path)
return any(fnmatch.fnmatch(base_name, p) for p in self.ignore_dirs)
def is_ignored_file(self, full_path):
type_, _ = mimetypes.guess_type(full_path)
if type_ is not None:
if type_.startswith('image/'):
return True
base_name = os.path.basename(full_path)
return any(fnmatch.fnmatch(base_name, p) for p in self.ignore_files)
def is_excluded(self, short_path):
app_dir, *_ = os.path.split(short_path)
return any(fnmatch.fnmatch(app_dir, p) for p in self.exclude)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment