Skip to content
This repository has been archived by the owner on Sep 6, 2019. It is now read-only.

Commit

Permalink
Merge branch 'feature/process-single-template' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
sergei-maertens committed Apr 26, 2016
2 parents 5f18459 + 33799cb commit bd385df
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 36 deletions.
7 changes: 7 additions & 0 deletions docs/getting_started.rst
Expand Up @@ -203,6 +203,13 @@ Options
python manage.py systemjs_bundle --sfx
* ``--template, -t``: pass the name of a template (for example ``myapp/base.html``),
and Django SystemJS will only look in those files for imported apps. It will
no longer parse all project templates. This option can be specified multiple
times to look in a set of templates.

.. note:: Added in 1.4.


Example workflow
================
Expand Down
103 changes: 67 additions & 36 deletions systemjs/management/commands/systemjs_bundle.py
@@ -1,6 +1,5 @@
from __future__ import unicode_literals

import argparse
import io
import os
import re
Expand All @@ -9,19 +8,23 @@
from django.conf import settings
from django.contrib.staticfiles.storage import staticfiles_storage
from django.core.exceptions import SuspiciousFileOperation
from django.core.management.base import BaseCommand
from django.core.management.base import BaseCommand, CommandError
from django.core.management.utils import handle_extensions
from django.core.files.storage import FileSystemStorage
from django.template.base import TOKEN_BLOCK
from django.template import loader, TemplateDoesNotExist
from django.template.loaders.app_directories import get_app_template_dirs

from systemjs.base import System
from systemjs.compat import Lexer
from systemjs.jspm import find_systemjs_location
from systemjs.templatetags.system_tags import SystemImportNode


SYSTEMJS_TAG_RE = re.compile(r"""systemjs_import\s+(['\"])(?P<app>.*)\1""")

RESOLVE_CONTEXT = {}


class Command(BaseCommand):
help = "Find {% systemjs_import %} tags and bundle the JS apps."
Expand All @@ -42,40 +45,35 @@ def add_arguments(self, parser):

parser.add_argument('--minify', action='store_true', help='Let jspm minify the bundle')

parser.add_argument(
tpl_group = parser.add_mutually_exclusive_group()
tpl_group.add_argument(
'--extension', '-e', dest='extensions',
help='The file extension(s) to examine (default: "html"). Separate '
'multiple extensions with commas, or use -e multiple times.',
action='append')
tpl_group.add_argument(
'--template', '-t', dest='templates',
help='The templates to examine. Separate multiple template names with'
'commas, or use -t multiple times',
action='append')

parser.add_argument(
'--symlinks', '-s', action='store_true', dest='symlinks',
default=False, help='Follows symlinks to directories when examining '
'source code and templates for translation strings.')
'source code and templates for SystemJS imports.')
parser.add_argument(
'--no-post-process',
action='store_false', dest='post_process', default=True,
help="Do NOT post process collected files.")

def handle(self, **options):
self.verbosity = 2
self.storage = staticfiles_storage
self.storage.systemjs_bundling = True # set flag to check later
extensions = options.get('extensions') or ['html']
self.symlinks = options.get('symlinks')
self.post_process = options['post_process']
# self.post_processed_files = []

self.extensions = handle_extensions(extensions)

def discover_templates(self):
template_dirs = list(get_app_template_dirs('templates'))
for config in settings.TEMPLATES:
# only support vanilla Django templates
if config['BACKEND'] != 'django.template.backends.django.DjangoTemplates':
continue
template_dirs += list(config['DIRS'])

# find all template files

all_files = []
for template_dir in template_dirs:
for dirpath, dirnames, filenames in os.walk(template_dir, topdown=True, followlinks=self.symlinks):
Expand All @@ -86,16 +84,49 @@ def handle(self, **options):
continue
all_files.append(filepath)

all_apps = []
for fp in all_files:
with io.open(fp, 'r', encoding=settings.FILE_CHARSET) as template_file:
src_data = template_file.read()
return all_files

for t in Lexer(src_data).tokenize():
if t.token_type == TOKEN_BLOCK:
imatch = SYSTEMJS_TAG_RE.match(t.contents)
if imatch:
all_apps.append(imatch.group('app'))
def handle(self, **options):
self.verbosity = 2
self.storage = staticfiles_storage
self.storage.systemjs_bundling = True # set flag to check later
extensions = options.get('extensions') or ['html']
self.symlinks = options.get('symlinks')
self.post_process = options['post_process']
# self.post_processed_files = []

self.extensions = handle_extensions(extensions)

# find all template files
all_apps = []
if not options.get('templates'):

all_files = self.discover_templates()
for fp in all_files:
with io.open(fp, 'r', encoding=settings.FILE_CHARSET) as template_file:
src_data = template_file.read()

for t in Lexer(src_data).tokenize():
if t.token_type == TOKEN_BLOCK:
imatch = SYSTEMJS_TAG_RE.match(t.contents)
if imatch:
all_apps.append(imatch.group('app'))
else:
for tpl in options.get('templates'):
try:
template = loader.get_template(tpl)
except TemplateDoesNotExist:
raise CommandError('Template \'%s\' does not exist' % tpl)
import_nodes = template.template.nodelist.get_nodes_by_type(SystemImportNode)
for node in import_nodes:
app = node.path.resolve(RESOLVE_CONTEXT)
if not app:
self.stdout.write(self.style.WARNING(
'{tpl}: Could not resolve path with context {ctx}, skipping.'.format(
tpl=tpl, ctx=RESOLVE_CONTEXT)
))
continue
all_apps.append(app)

bundled_files = OrderedDict()
# FIXME: this should be configurable, if people use S3BotoStorage for example, it needs to end up there
Expand All @@ -108,17 +139,17 @@ def handle(self, **options):
self.stdout.write('Bundled {app} into {out}'.format(app=app, out=rel_path))
bundled_files[rel_path] = (storage, rel_path)

# post-process system.js if it's within settings.STATIC_ROOT
systemjs_path = find_systemjs_location()
try:
within_static_root = self.storage.exists(systemjs_path)
except SuspiciousFileOperation:
within_static_root = False
if within_static_root:
relative = os.path.relpath(systemjs_path, settings.STATIC_ROOT)
bundled_files[relative] = (storage, relative)

if self.post_process and hasattr(self.storage, 'post_process'):
# post-process system.js if it's within settings.STATIC_ROOT
systemjs_path = find_systemjs_location()
try:
within_static_root = self.storage.exists(systemjs_path)
except SuspiciousFileOperation:
within_static_root = False
if within_static_root:
relative = os.path.relpath(systemjs_path, settings.STATIC_ROOT)
bundled_files[relative] = (storage, relative)

processor = self.storage.post_process(bundled_files, dry_run=False)
for original_path, processed_path, processed in processor:
if isinstance(processed, Exception): # pragma: no cover
Expand Down
19 changes: 19 additions & 0 deletions tests/tests/test_management.py
Expand Up @@ -143,6 +143,25 @@ def test_skip_post_process(self, bundle_mock):
self.assertEqual(_num_files(settings.STATIC_ROOT), 1)
self.assertEqual(bundle_mock.call_count, 1) # only one bundle call made

def test_templates_option(self, bundle_mock):
bundle_mock.side_effect = _bundle

self.assertEqual(_num_files(settings.STATIC_ROOT), 0)
call_command('systemjs_bundle', '--template', 'base.html', stdout=self.out, stderr=self.err)
self.assertEqual(_num_files(settings.STATIC_ROOT), 1)

self.assertEqual(bundle_mock.call_count, 1) # only one app should be found
self.assertEqual(bundle_mock.call_args, mock.call('app/dummy', force=True, sfx=False, minify=False))

def test_templates_option_wrong_tpl(self, bundle_mock):
bundle_mock.side_effect = _bundle

self.assertEqual(_num_files(settings.STATIC_ROOT), 0)
with self.assertRaises(CommandError):
call_command('systemjs_bundle', '--template', 'nothere.html', stdout=self.out, stderr=self.err)
self.assertEqual(_num_files(settings.STATIC_ROOT), 0)
self.assertEqual(bundle_mock.call_count, 0)


@override_settings(STATIC_ROOT=tempfile.mkdtemp())
class FailedBundleTests(MockFindSystemJSLocation, ClearStaticMixin, SimpleTestCase):
Expand Down

0 comments on commit bd385df

Please sign in to comment.