diff --git a/hshassets/app_settings/defaults.py b/hshassets/app_settings/defaults.py index 504c01d2205c6c632d86a0a6e7486adf38fd4394..465d194871a48226e904c3df2811b458b03aef83 100644 --- a/hshassets/app_settings/defaults.py +++ b/hshassets/app_settings/defaults.py @@ -28,5 +28,3 @@ SCSS_INCLUDE_PATHS = [ # URI to the live service.it portal SERVICE_IT_URI = 'https://service.it.hs-hannover.de/' - - diff --git a/hshassets/management/commands/watchassets.py b/hshassets/management/commands/watchassets.py index 5d745531b37506c572953d4f642314457e9838a8..5f143becc9325389585cd7f172e1ed1d7e6beb5f 100644 --- a/hshassets/management/commands/watchassets.py +++ b/hshassets/management/commands/watchassets.py @@ -1,44 +1,76 @@ from django.core.management.base import BaseCommand from hshassets import utils +from hshassets import logger import os +import time +import shutil import traceback from watchdog import events from watchdog.observers import Observer -class AssetBuildHandler(events.PatternMatchingEventHandler): - - def __init__(self, scss_include_paths, *args, **kwargs): - super().__init__(*args, **kwargs) - self.scss_include_paths = scss_include_paths - - def on_modified(self, event): - if isinstance(event, events.FileModifiedEvent): - print('File modified: \033[35m{}\033[0m'.format(event.src_path)) - try: - app_name, app_directories = utils.discover_app(event.src_path) - utils.build_specific(app_name, app_directories, event.src_path, self.scss_include_paths) - except Exception as e: - print('Failed! \033[31m(✖_✖)\033[0m\n') - traceback.print_exc() - - -class ImageHandler(events.PatternMatchingEventHandler): - - def dispatch(self, event): - print('Image change detected, \033[35m{}\033[0m'.format(event.src_path)) - app_name, app_directories = utils.discover_app(event.src_path) - utils.copy_images(app_directories) - - -class FontHandler(events.PatternMatchingEventHandler): - - def dispatch(self, event): - print('Font change detected, \033[35m{}\033[0m'.format(event.src_path)) - app_name, app_directories = utils.discover_app(event.src_path) - utils.copy_fonts(app_directories) +class AssetsHandler(events.FileSystemEventHandler): + + def on_created(self, event): + app_name = utils.get_appname_from_assets_path(event.src_path) + if not event.is_directory: + logger.info("Found new file {} in {}.".format(os.path.basename(event.src_path), event.src_path)) + file_extension = utils.get_file_extension(event.src_path) + if file_extension == 'css': + utils.copy_and_compile_assets_css_to_static(app_name) + elif file_extension == 'js': + utils.copy_and_compile_assets_js_to_static(app_name) + elif file_extension == 'scss': + utils.build_app_css_from_scss(app_name) + else: + static_path = utils.get_static_path(app_name) + assets_path = utils.get_assets_path(app_name) + destination_path = str(event.src_path).replace(assets_path, static_path) + source_path = os.path.dirname(event.src_path) + destination_path = os.path.dirname(destination_path) + utils.copy_directory_tree(source_path, destination_path) + + def on_deleted(self, event): + app_name = utils.get_appname_from_assets_path(event.src_path) + base = os.path.basename(event.src_path) + subfolder = utils.get_assets_subdirectory(event.src_path) + if event.is_directory: + if subfolder == base or subfolder not in ['css', 'js', 'sass']: + utils.remove_static_content(app_name, event.src_path) + if subfolder == 'css': + utils.copy_and_compile_assets_css_to_static(app_name) + elif subfolder == 'js': + utils.copy_and_compile_assets_js_to_static(app_name) + elif subfolder == 'sass': + logger.info("Noticed directory 'sass' has been deleted.") + styles = os.path.join(utils.get_static_path(app_name), 'styles.min.css') + utils.remove_file(styles) + styles = os.path.join(utils.get_static_path(app_name), 'styles.css') + utils.remove_file(styles) + else: + logger.info("Noticed '{}' has been deleted".format(base)) + static_path = utils.get_static_path(app_name) + assets_path = utils.get_assets_path(app_name) + path = str(event.src_path).replace(assets_path, static_path) + utils.remove_empty_directory(os.path.basename(path[0])) + else: + extension = utils.get_file_extension(event.src_path) + if extension == subfolder in ['js', 'css']: + utils.remove_static_minimized_file(app_name, event.src_path) + elif base == '_init.scss': + logger.info("Noticed '_init.scss' has been deleted.") + utils.build_app_css_from_scss(app_name) + sass_path = os.path.join(utils.get_assets_path(app_name), 'sass') + utils.remove_empty_directory(sass_path) + else: + logger.info("Noticed '{}' has been deleted".format(base)) + static_path = utils.get_static_path(app_name) + assets_path = utils.get_assets_path(app_name) + path = str(event.src_path).replace(assets_path, static_path) + utils.remove_file(path) + utils.remove_empty_directory(os.path.basename(path[0])) class Command(BaseCommand): @@ -46,29 +78,27 @@ class Command(BaseCommand): def handle(self, *args, **options): print('Building assets initially ...') - utils.do_everything() + utils.build_assets() print('Done. Watching now for changes \033[94m(ಠ_ಠ)\033[0m ...\n') observer = Observer() - asset_directories = utils.get_asset_directories() - scss_include_paths = utils.get_scss_include_paths(asset_directories) + assets_handler = AssetsHandler() - build_handler = AssetBuildHandler(scss_include_paths, patterns=['*.js', '*.sass', '*.scss', '*.css']) - image_handler = ImageHandler(patterns=['*']) - for app_name, app_directories in asset_directories.items(): + apps = utils.get_installed_apps_with_asset_directory(include_project=True) - if not os.path.isdir(app_directories['static_path']): - os.makedirs(app_directories['static_path']) + for app_name in apps: - observer.schedule(build_handler, app_directories['asset_path'], recursive=True) + asset_path = utils.get_assets_path(app_name) - if os.path.isdir(app_directories['asset_path'] + '/img'): - observer.schedule(image_handler, app_directories['asset_path'] + '/img', recursive=True) + observer.schedule(assets_handler, asset_path, recursive=True) observer.start() try: - observer.join() + while True: + time.sleep(1) except KeyboardInterrupt: + observer.stop() print('\033[2DBye \033[95mʕʘ‿ʘʔ\033[0m') + observer.join() diff --git a/hshassets/tests/__init__.py b/hshassets/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/hshassets/tests/test_utils.py b/hshassets/tests/test_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..ff28114176e70f37585e7585937fbc255610bb37 --- /dev/null +++ b/hshassets/tests/test_utils.py @@ -0,0 +1,17 @@ +from django.test import TestCase +from hshassets.utils import ( + get_file_extension, +) + + +""" +If you want to execute tests, please make sure your user has rights to create test database: +You can alter user permissions in your postgres database by using: ALTER USER username CREATEDB; +""" +class UtilsTestCase(TestCase): + def test_get_file_extension(self): + """Get correct extension of file.""" + self.assertEqual(get_file_extension("/my/path/to/file/base.js"), "js") + self.assertEqual(get_file_extension("/my/path/to/file/base.js.js"), "js") + self.assertEqual(get_file_extension("/my/path/to/file/base"), "") + diff --git a/hshassets/utils.py b/hshassets/utils.py index 96e4e096dfe19f0bc5bbc31e391acbc07b419a80..012f2fee388906edab6d90d8965a2b244f23649f 100644 --- a/hshassets/utils.py +++ b/hshassets/utils.py @@ -170,6 +170,27 @@ def remove_empty_directory(path): remove_empty_directory(path) +def remove_directory(path): + """ + Delete subdirectory of path even if empty or not. + :param path: as string + :return: no return + """ + if os.path.isdir(path): + shutil.rmtree(path) + + +def remove_file(path): + """ + Delete file + :param path: as string + :return: no return + """ + if os.path.isfile(path): + os.remove(path) + logger.info("Delete file '{}' in '{}'.".format(os.path.split(path)[-1], os.path.split(path)[0])) + + def remove_static_minimized_file(app_name, source_path): """ Removes the js and js min file if found in static directory of app_name. @@ -178,9 +199,9 @@ def remove_static_minimized_file(app_name, source_path): :return: no return """ - subdirectory = str(get_file_extension(source_path)).strip('.') + extension = get_file_extension(source_path) - static_path = os.path.join(get_static_path(app_name), subdirectory) + static_path = os.path.join(get_static_path(app_name), extension) file_name = os.path.basename(source_path) min_file_name = get_min_name(file_name) @@ -196,6 +217,21 @@ def remove_static_minimized_file(app_name, source_path): remove_empty_directory(static_path) +def remove_static_content(app_name, path): + """ + Removes static file or directory according to an asset file or directory that has been removed. + :param app_name: as string + :param path: assets path as string + :return: no return + """ + static_path = get_static_path(app_name) + assets_path = get_assets_path(app_name) + dest_path = str(path).replace(assets_path, static_path) + if os.path.exists(dest_path): + shutil.rmtree(dest_path) + remove_empty_directory(os.path.dirname(dest_path)) + + def minimize_js(app_name, js_file_path): """ Minimize js file which means remove whitspaces and calls function write_files to save minimized js in static @@ -338,6 +374,7 @@ def copy_directory_tree(source, destination): (os.path.join(source)), (os.path.join(destination)) ) + logger.info("Copy assets directory '{}' to '{}'.".format(os.path.basename(source), destination)) def copy_and_compile_assets_css_to_static(app_name): @@ -467,9 +504,11 @@ def init_assets(name=None, empty_scss=False): # create empty scss file init_file = open(os.path.join(sass_path, '_init.scss'), 'w') init_file.close() + logger.info("Create '_init.scss' in '{}'.".format(get_sass_path(name))) else: # copy default scss file from hshassets shutil.copyfile(get_default_scss_path(), os.path.join(get_sass_path(name), '_init.scss')) + logger.info("Save '_init.scss' in '{}'.".format(get_sass_path(name))) def build_specific(app_name, file_path): @@ -507,9 +546,9 @@ def get_assets_subdirectory(path): def get_file_extension(path): """ - Returns the extension of path, for example "/home/..../base.js" returns ".js". If no extension found - empty string will be returned + Returns the extension of path, for example "/home/..../base.js" returns "js". + If no extension found empty string will be returned :param path: path as string :return: extension as string or empty string """ - return os.path.splitext(path)[-1] + return (os.path.splitext(path)[-1]).strip(".") diff --git a/setup.py b/setup.py index aecb32a3e1e6253f2dbdd7f9326fbd63c916b549..ec0e03fdf4dce968653556984b21c5eab9139d1a 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir))) setup( name='django-hshassets', - version='2.1.0', + version='2.1.2', packages=find_packages(), include_package_data=True, license='MIT License',