Skip to content

Commit f309653

Browse files
author
Andrey Fedoseev
committed
Add --delete-stale-files option to compilestatic command
1 parent 03613b3 commit f309653

File tree

4 files changed

+72
-3
lines changed

4 files changed

+72
-3
lines changed

CHANGES.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Dev
77

88
- Remove deprecated settings: ``COFFEESCRIPT_EXECUTABLE``, ``SCSS_EXECUTABLE``, ``SCSS_USE_COMPASS``, ``LESS_EXECUTABLE``
99
- Add ``--ignore-dependencies`` option to ``compilestatic`` command
10+
- Add ``--delete-stale-files`` option to ``compilestatic`` command
1011

1112
1.7.1
1213
=====

docs/compilestatic-command.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ Sometimes it may be useful to prevent dependency tracking when running ``compile
1313
have access to a database (building a Docker image). Use ``--ignore-dependencies`` option to disable the dependency
1414
tracking.
1515

16+
``--delete-stale-files`` option may be used to delete compiled files that no longer have matching source files.
17+
Example: you have a ``styles.scss`` which get compiled to ``styles.css``. If you remove the source file ``styles.scss``
18+
and run ``compilestatic --delete-stale-files`` it will compile the files as usual, and delete the stale ``styles.css``
19+
file.
20+
1621
You can run ``compilestatic`` in watch mode (``--watch`` option). In watch mode it will monitor the changes in your
1722
source files and re-compile them on the fly. It can be handy if you use tools such as
1823
`LiveReload <http://livereload.com/>`_.

static_precompiler/management/commands/compilestatic.py

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import django.core.files.storage
77
import django.core.management.base
88

9-
from ... import exceptions, registry, settings
9+
from ... import exceptions, registry, settings, utils
1010

1111

1212
def get_scanned_dirs():
@@ -31,13 +31,32 @@ def list_files(scanned_dirs):
3131
yield path
3232

3333

34+
def delete_stale_files(compiled_files):
35+
compiled_files = set(
36+
os.path.join(settings.ROOT, utils.normalize_path(compiled_file)) for compiled_file in compiled_files
37+
)
38+
actual_files = set()
39+
for dirname, dirnames, filenames in os.walk(os.path.join(settings.ROOT, settings.OUTPUT_DIR)):
40+
for filename in filenames:
41+
actual_files.add(os.path.join(dirname, filename))
42+
stale_files = actual_files - compiled_files
43+
for stale_file in stale_files:
44+
os.remove(stale_file)
45+
46+
3447
ARGUMENTS = (
3548
("--ignore-dependencies", dict(
3649
action="store_true",
3750
dest="ignore_dependencies",
3851
default=False,
3952
help="Disable dependency tracking, this prevents any database access.",
40-
)),
53+
)),
54+
("--delete-stale-files", dict(
55+
action="store_true",
56+
dest="delete_stale_files",
57+
default=False,
58+
help="Delete compiled files don't have matching source files.",
59+
)),
4160
("--watch", dict(
4261
action="store_true",
4362
dest="watch",
@@ -78,6 +97,7 @@ def handle(self, **options):
7897

7998
if not options["watch"] or options["initial_scan"]:
8099
# Scan the watched directories and compile everything
100+
compiled_files = set()
81101
for path in sorted(set(list_files(scanned_dirs))):
82102
for compiler in compilers:
83103
if compiler.is_supported(path):
@@ -89,10 +109,15 @@ def handle(self, **options):
89109
continue
90110

91111
try:
92-
compiler.compile(path, from_management=True, verbosity=verbosity)
112+
compiled_files.add(
113+
compiler.compile(path, from_management=True, verbosity=verbosity)
114+
)
93115
except (exceptions.StaticCompilationError, ValueError) as e:
94116
print(e)
95117

118+
if options["delete_stale_files"]:
119+
delete_stale_files(compiled_files)
120+
96121
if options["watch"]:
97122
from static_precompiler.watch import watch_dirs
98123
watch_dirs(scanned_dirs, verbosity)

static_precompiler/tests/test_management.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,41 @@ def test_ignore_dependencies_option(django_assert_num_queries, monkeypatch, tmpd
6666

6767
with django_assert_num_queries(0):
6868
management.call_command("compilestatic", ignore_dependencies=True)
69+
70+
71+
@pytest.mark.django_db
72+
def test_delete_stale_files(monkeypatch, tmpdir):
73+
74+
output_path = os.path.join(tmpdir.strpath, static_precompiler.settings.OUTPUT_DIR)
75+
if not os.path.exists(output_path):
76+
os.makedirs(output_path)
77+
78+
unmanaged_file = os.path.join(tmpdir.strpath, "unmanaged.js")
79+
with open(unmanaged_file, "w+") as f:
80+
f.write("unmanaged")
81+
82+
with open(os.path.join(output_path, "stale.js"), "w+") as f:
83+
f.write("stale")
84+
85+
monkeypatch.setattr("static_precompiler.management.commands.compilestatic.get_scanned_dirs", lambda: (
86+
os.path.join(os.path.dirname(__file__), "compilestatic"),
87+
))
88+
monkeypatch.setattr("static_precompiler.settings.ROOT", tmpdir.strpath)
89+
90+
management.call_command("compilestatic", delete_stale_files=True)
91+
92+
compiled_files = []
93+
for root, dirs, files in os.walk(output_path):
94+
for filename in files:
95+
compiled_files.append(os.path.join(root[len(output_path):].lstrip("/"), filename))
96+
97+
compiled_files.sort()
98+
99+
assert compiled_files == [
100+
"coffee/test.js",
101+
"less/test.css",
102+
"scss/test.css",
103+
]
104+
105+
# Files outside of `COMPILED` directory are untouched
106+
assert os.path.exists(unmanaged_file)

0 commit comments

Comments
 (0)