diff --git a/djangobwr/__init__.py b/djangobwr/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/djangobwr/finders.py b/djangobwr/finders.py new file mode 100644 index 000000000..16989f7dc --- /dev/null +++ b/djangobwr/finders.py @@ -0,0 +1,10 @@ +from django.contrib.staticfiles.finders import AppDirectoriesFinder + +class AppDirectoriesFinderBower(AppDirectoriesFinder): + + def list(self, ignore_patterns): + """ + List all files in all app storages. + """ + ignore_patterns.append("bower_components") + return super(AppDirectoriesFinderBower, self).list(ignore_patterns) diff --git a/djangobwr/management/__init__.py b/djangobwr/management/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/djangobwr/management/commands/__init__.py b/djangobwr/management/commands/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/djangobwr/management/commands/bower_install.py b/djangobwr/management/commands/bower_install.py new file mode 100644 index 000000000..de6e5b0c0 --- /dev/null +++ b/djangobwr/management/commands/bower_install.py @@ -0,0 +1,232 @@ +import os +import sys +import json +import tempfile +import shutil +import hashlib +import glob +import textwrap +from subprocess import call, check_output, CalledProcessError +from optparse import make_option + +import debug # pyflakes:ignore + +from django.core.management.base import BaseCommand +from django.core.files.storage import FileSystemStorage +from django.conf import settings +from django.contrib.staticfiles.finders import BaseStorageFinder, AppDirectoriesFinder + +class BaseDirectoryFinder(BaseStorageFinder): + storage = FileSystemStorage(location=settings.BASE_DIR) + +class Command(BaseCommand): + """ + + This command goes through any static/ directories of installed apps, + and the directories listed in settings.STATICFILES_DIRS. If package + description files for bower, npm, or grunt are found in any of these + locations, it will use the appropriate package manager to install the + listed packages in a temporary folder, using these commands: + + - package.json: npm install + - Gruntfile.js: grunt default + - bower.json: bower install + + It will then extract the distribution files to the location indicated in + settings.COMPONENT_ROOT. + """ + help = textwrap.dedent(__doc__).lstrip() + component_root = getattr(settings, 'COMPONENT_ROOT', os.path.join(settings.STATIC_ROOT, "components")) + + def add_arguments(self, parser): + parser.add_argument('--with-version', dest='with_version', default=False, action='store_true', + help='Create component directories with version numbers') + parser.add_argument('--keep-packages', dest='keep_packages', default=False, action='store_true', + help='Keep the downloaded bower packages, instead of removing them after moving ' + 'distribution files to settings.COMPONENT_ROOT') + + bower_info = {} + overrides = {} + + def npm_install(self, pkg_json_path): + os.chdir(os.path.dirname(pkg_json_path)) + call(['npm', 'install']) + + def grunt_default(self, grunt_js_path): + os.chdir(os.path.dirname(grunt_js_path)) + call(['grunt']) + + def bower_install(self, bower_json_path, dest_dir): + """Runs bower commnand for the passed bower.json path. + + :param bower_json_path: bower.json file to install + :param dest_dir: where the compiled result will arrive + """ + + # Verify that we are able to run bower, in order to give a good error message in the + # case that it's not installed. Do this separately from the 'bower install' call, in + # order not to warn about a missing bower in the case of installation-related errors. + try: + bower_version = check_output(['bower', '--version']).strip() + except OSError as e: + print("Trying to run bower failed -- is it installed? The error was: %s" % e) + exit(1) + except CalledProcessError as e: + print("Checking the bower version failed: %s" % e) + exit(2) + + print("\nBower %s" % bower_version) + print("Installing from %s\n" % bower_json_path) + + # bower args + args = ['bower', 'install', bower_json_path, + '--verbose', '--config.cwd={}'.format(dest_dir), '-p'] + + # run bower command + call(args) + + def get_bower_info(self, bower_json_path): + if not bower_json_path in self.bower_info: + self.bower_info[bower_json_path] = json.load(open(bower_json_path)) + + def get_bower_main_list(self, bower_json_path, override): + """ + Returns the bower.json main list or empty list. + Applies overrides from the site-wide bower.json. + """ + self.get_bower_info(bower_json_path) + + main_list = self.bower_info[bower_json_path].get('main') + component = self.bower_info[bower_json_path].get('name') + + if (override in self.bower_info + and "overrides" in self.bower_info[override] + and component in self.bower_info[override].get("overrides") + and "main" in self.bower_info[override].get("overrides").get(component)): + main_list = self.bower_info[override].get("overrides").get(component).get("main") + + if isinstance(main_list, list): + return main_list + + if main_list: + return [main_list] + + return [] + + def get_bower_version(self, bower_json_path): + """Returns the bower.json main list or empty list. + """ + self.get_bower_info(bower_json_path) + + return self.bower_info[bower_json_path].get("version") + + def clean_components_to_static_dir(self, bower_dir, override): + print("\nMoving component files to %s\n" % (self.component_root,)) + + for directory in os.listdir(bower_dir): + print("Component: %s" % (directory, )) + + src_root = os.path.join(bower_dir, directory) + + for bower_json in ['bower.json', '.bower.json']: + bower_json_path = os.path.join(src_root, bower_json) + if os.path.exists(bower_json_path): + main_list = self.get_bower_main_list(bower_json_path, override) + ['bower.json'] + version = self.get_bower_version(bower_json_path) + + dst_root = os.path.join(self.component_root, directory) + if self.with_version: + assert not dst_root.endswith(os.sep) + dst_root += "-"+version + + for pattern in filter(None, main_list): + src_pattern = os.path.join(src_root, pattern) + # main_list elements can be fileglob patterns + for src_path in glob.glob(src_pattern): + if not os.path.exists(src_path): + print("Could not find source path: %s" % (src_path, )) + + # Build the destination path + src_part = src_path[len(src_root+'/'):] + if src_part.startswith('dist/'): + src_part = src_part[len('dist/'):] + dst_path = os.path.join(dst_root, src_part) + + # Normalize the paths, for good looks + src_path = os.path.abspath(src_path) + dst_path = os.path.abspath(dst_path) + + # Check if we need to copy the file at all. + if os.path.exists(dst_path): + with open(src_path) as src: + src_hash = hashlib.sha1(src.read()).hexdigest() + with open(dst_path) as dst: + dst_hash = hashlib.sha1(dst.read()).hexdigest() + if src_hash == dst_hash: + #print('{0} = {1}'.format(src_path, dst_path)) + continue + + # Make sure dest dir exists. + dst_dir = os.path.dirname(dst_path) + if not os.path.exists(dst_dir): + os.makedirs(dst_dir) + + print(' {0} > {1}'.format(src_path, dst_path)) + shutil.copy(src_path, dst_path) + break + + def handle(self, *args, **options): + + self.with_version = options.get("with_version") + self.keep_packages = options.get("keep_packages") + + temp_dir = getattr(settings, 'BWR_APP_TMP_FOLDER', 'tmp') + temp_dir = os.path.abspath(temp_dir) + + # finders + basefinder = BaseDirectoryFinder() + appfinder = AppDirectoriesFinder() + # Assume bower.json files are to be found in each app directory, + # rather than in the app's static/ subdirectory: + appfinder.source_dir = '.' + + finders = (basefinder, appfinder, ) + + if os.path.exists(temp_dir): + if not self.keep_packages: + sys.stderr.write( + "\nWARNING:\n\n" + " The temporary package installation directory exists, but the --keep-packages\n" + " option has not been given. In order to not delete anything which should be\n" + " kept, %s will not be removed.\n\n" + " Please remove it manually, or use the --keep-packages option to avoid this\n" + " message.\n\n" % (temp_dir,)) + self.keep_packages = True + else: + os.makedirs(temp_dir) + + for finder in finders: + for path in finder.find('package.json', all=True): + self.npm_install(path) + + for finder in finders: + for path in finder.find('Gruntfile.json', all=True): + self.grunt_default(path) + + for finder in finders: + for path in finder.find('bower.json', all=True): + self.get_bower_info(path) + self.bower_install(path, temp_dir) + + bower_dir = os.path.join(temp_dir, 'bower_components') + + # nothing to clean + if not os.path.exists(bower_dir): + print('No components seems to have been found by bower, exiting.') + sys.exit(0) + + self.clean_components_to_static_dir(bower_dir, path) + + if not self.keep_packages: + shutil.rmtree(temp_dir) + diff --git a/djangobwr/models.py b/djangobwr/models.py new file mode 100644 index 000000000..71a836239 --- /dev/null +++ b/djangobwr/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/djangobwr/storage.py b/djangobwr/storage.py new file mode 100644 index 000000000..e69de29bb