| 
									
										
										
										
											2018-02-05 02:47:18 +05:30
										 |  |  | """
 | 
					
						
							|  |  |  | This command finds and creates a report for all the usage of css rules in | 
					
						
							|  |  |  | an app. It aims to optimize existing codebase as well as assist the frontend | 
					
						
							|  |  |  | developer when designing new components by avoiding unnecessary duplication and | 
					
						
							|  |  |  | suggesting more/optimal alternatives. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Features: | 
					
						
							|  |  |  |     Currently the command can find out and display: | 
					
						
							|  |  |  |         - Media Breakpoints used in a stylesheet | 
					
						
							|  |  |  |         - Duplicate selectors in a stylesheet | 
					
						
							|  |  |  |         - Unused selectors | 
					
						
							|  |  |  |     Work in progress to enable these features: | 
					
						
							|  |  |  |         - Duplicate style declaration for same selector | 
					
						
							|  |  |  |         - DOM validation | 
					
						
							|  |  |  |         - Finding out dead styles (those that are always cancelled) | 
					
						
							|  |  |  |         - Optimize media declarations | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Example: | 
					
						
							|  |  |  |     $ python manage.py optimize_frontend datacenterlight | 
					
						
							|  |  |  |     above command produces a file ../optimize_frontend.html which contains a | 
					
						
							|  |  |  |     report with the above mentioned features | 
					
						
							|  |  |  | """
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # import csv | 
					
						
							| 
									
										
										
										
											2018-02-08 19:10:37 +05:30
										 |  |  | import json | 
					
						
							| 
									
										
										
										
											2018-01-30 19:09:55 +05:30
										 |  |  | import logging | 
					
						
							|  |  |  | import os | 
					
						
							|  |  |  | import re | 
					
						
							|  |  |  | from collections import Counter, OrderedDict | 
					
						
							| 
									
										
										
										
											2018-02-05 02:47:18 +05:30
										 |  |  | # from itertools import zip_longest | 
					
						
							| 
									
										
										
										
											2018-01-30 19:09:55 +05:30
										 |  |  | 
 | 
					
						
							|  |  |  | from django import template | 
					
						
							|  |  |  | from django.conf import settings | 
					
						
							|  |  |  | from django.contrib.staticfiles import finders | 
					
						
							|  |  |  | from django.core.management.base import BaseCommand | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | logger = logging.getLogger(__name__) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | RE_PATTERNS = { | 
					
						
							|  |  |  |     'view_html': '[\'\"](.*\.html)', | 
					
						
							|  |  |  |     'html_html': '{% (?:extends|include) [\'\"]?(.*\.html)', | 
					
						
							|  |  |  |     'html_style': '{% static [\'\"]?(.*\.css)', | 
					
						
							| 
									
										
										
										
											2018-02-04 03:20:27 +05:30
										 |  |  |     'css_media': ( | 
					
						
							|  |  |  |         '^\s*\@media([^{]+)\{\s*([\s\S]*?})\s*}' | 
					
						
							|  |  |  |     ), | 
					
						
							| 
									
										
										
										
											2018-01-30 19:09:55 +05:30
										 |  |  |     'css_selector': ( | 
					
						
							| 
									
										
										
										
											2018-02-08 19:10:37 +05:30
										 |  |  |         '^\s*([.#\[:_A-Za-z][^{]*?)\s*' | 
					
						
							| 
									
										
										
										
											2018-02-13 19:03:15 +05:30
										 |  |  |         '\s*{\s*([\s\S]*?)\s*}' | 
					
						
							| 
									
										
										
										
											2018-01-30 19:09:55 +05:30
										 |  |  |     ), | 
					
						
							|  |  |  |     'html_class': 'class=[\'\"]([a-zA-Z0-9-_\s]*)', | 
					
						
							|  |  |  |     'html_id': 'id=[\'\"]([a-zA-Z0-9-_]*)' | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class Command(BaseCommand): | 
					
						
							| 
									
										
										
										
											2018-02-05 02:47:18 +05:30
										 |  |  |     help = ( | 
					
						
							|  |  |  |         'Finds unused and duplicate style declarations from the stylesheets ' | 
					
						
							|  |  |  |         'used in the templates of each app' | 
					
						
							|  |  |  |     ) | 
					
						
							| 
									
										
										
										
											2018-01-30 19:09:55 +05:30
										 |  |  |     requires_system_checks = False | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def add_arguments(self, parser): | 
					
						
							|  |  |  |         # positional arguments | 
					
						
							|  |  |  |         parser.add_argument( | 
					
						
							|  |  |  |             'apps', nargs='+', type=str, | 
					
						
							|  |  |  |             help='name of the apps to be optimized' | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Named (optional) arguments | 
					
						
							|  |  |  |         parser.add_argument( | 
					
						
							|  |  |  |             '--together', | 
					
						
							|  |  |  |             action='store_true', | 
					
						
							|  |  |  |             help='optimize the apps together' | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |         parser.add_argument( | 
					
						
							|  |  |  |             '--css', | 
					
						
							|  |  |  |             action='store_true', | 
					
						
							| 
									
										
										
										
											2018-02-05 02:47:18 +05:30
										 |  |  |             help='optimize only the css rules declared in each stylesheet' | 
					
						
							| 
									
										
										
										
											2018-01-30 19:09:55 +05:30
										 |  |  |         ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def handle(self, *args, **options): | 
					
						
							|  |  |  |         apps_list = options['apps'] | 
					
						
							| 
									
										
										
										
											2018-02-08 19:10:37 +05:30
										 |  |  |         report = {} | 
					
						
							| 
									
										
										
										
											2018-01-30 19:09:55 +05:30
										 |  |  |         for app in apps_list: | 
					
						
							|  |  |  |             if options['css']: | 
					
						
							| 
									
										
										
										
											2018-02-08 19:10:37 +05:30
										 |  |  |                 report[app] = self.optimize_css(app) | 
					
						
							|  |  |  |         # write report | 
					
						
							|  |  |  |         write_report(report) | 
					
						
							| 
									
										
										
										
											2018-01-30 19:09:55 +05:30
										 |  |  | 
 | 
					
						
							|  |  |  |     def optimize_css(self, app_name): | 
					
						
							| 
									
										
										
										
											2018-02-05 02:47:18 +05:30
										 |  |  |         """Optimize declarations inside a css stylesheet
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         Args: | 
					
						
							|  |  |  |             app_name (str): The application name | 
					
						
							|  |  |  |         """
 | 
					
						
							| 
									
										
										
										
											2018-01-30 19:09:55 +05:30
										 |  |  |         # get html and css files used in the app | 
					
						
							| 
									
										
										
										
											2018-02-04 03:20:27 +05:30
										 |  |  |         files = get_files(app_name) | 
					
						
							| 
									
										
										
										
											2018-01-30 19:09:55 +05:30
										 |  |  |         # get_selectors_from_css | 
					
						
							| 
									
										
										
										
											2018-02-04 03:20:27 +05:30
										 |  |  |         css_selectors = get_selectors_css(files['style']) | 
					
						
							| 
									
										
										
										
											2018-01-30 19:09:55 +05:30
										 |  |  |         # get_selectors_from_html | 
					
						
							| 
									
										
										
										
											2018-02-04 03:20:27 +05:30
										 |  |  |         html_selectors = get_selectors_html(files['html']) | 
					
						
							| 
									
										
										
										
											2018-02-05 02:47:18 +05:30
										 |  |  |         report = { | 
					
						
							|  |  |  |             'css_dup': get_css_duplication(css_selectors), | 
					
						
							|  |  |  |             'css_unused': get_css_unused(css_selectors, html_selectors) | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2018-02-08 19:10:37 +05:30
										 |  |  |         return report | 
					
						
							| 
									
										
										
										
											2018-02-04 03:20:27 +05:30
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def get_files(app_name): | 
					
						
							| 
									
										
										
										
											2018-02-05 02:47:18 +05:30
										 |  |  |     """Get all the `html` and `css` files used in an app.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Args: | 
					
						
							|  |  |  |         app_name (str): The application name | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Returns: | 
					
						
							|  |  |  |         dict: A dictonary containing Counter of occurence of each | 
					
						
							|  |  |  |         html and css file in `html` and `style` fields respectively. | 
					
						
							|  |  |  |         For example: | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             'html': {'datacenterlight/success.html': 1}, | 
					
						
							|  |  |  |             'style': {'datacenterlight/css/bootstrap.min.css': 2} | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2018-02-04 03:20:27 +05:30
										 |  |  |     # the view file for the app | 
					
						
							|  |  |  |     app_view = os.path.join(settings.PROJECT_DIR, app_name, 'views.py') | 
					
						
							|  |  |  |     # get template files called from the view | 
					
						
							|  |  |  |     all_html_list = file_match_pattern(app_view, 'view_html') | 
					
						
							|  |  |  |     # list of unique template files | 
					
						
							|  |  |  |     uniq_html_list = list(OrderedDict.fromkeys(all_html_list).keys()) | 
					
						
							|  |  |  |     # list of stylesheets | 
					
						
							|  |  |  |     all_style_list = [] | 
					
						
							|  |  |  |     file_patterns = ['html_html', 'html_style'] | 
					
						
							|  |  |  |     # get html and css files called from within templates | 
					
						
							|  |  |  |     i = 0 | 
					
						
							|  |  |  |     while i < len(uniq_html_list): | 
					
						
							|  |  |  |         template_name = uniq_html_list[i] | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             temp_files = templates_match_pattern( | 
					
						
							|  |  |  |                 template_name, file_patterns | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |         except template.exceptions.TemplateDoesNotExist as e: | 
					
						
							|  |  |  |             print("template file not found: ", str(e)) | 
					
						
							|  |  |  |             all_html_list = [ | 
					
						
							|  |  |  |                 h for h in all_html_list if h != template_name | 
					
						
							|  |  |  |             ] | 
					
						
							|  |  |  |             del uniq_html_list[i] | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             all_html_list.extend(temp_files[0]) | 
					
						
							|  |  |  |             uniq_html_list = list( | 
					
						
							|  |  |  |                 OrderedDict.fromkeys(all_html_list).keys() | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |             all_style_list.extend(temp_files[1]) | 
					
						
							|  |  |  |             i += 1 | 
					
						
							|  |  |  |     # counter dict for the html files called from view | 
					
						
							|  |  |  |     result = { | 
					
						
							|  |  |  |         'html': Counter(all_html_list), | 
					
						
							|  |  |  |         'style': Counter(all_style_list) | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-02-05 02:47:18 +05:30
										 |  |  |     # print(result) | 
					
						
							| 
									
										
										
										
											2018-02-04 03:20:27 +05:30
										 |  |  |     return result | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def get_selectors_css(files): | 
					
						
							| 
									
										
										
										
											2018-02-05 02:47:18 +05:30
										 |  |  |     """Gets the selectors and declarations from a stylesheet.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Args: | 
					
						
							|  |  |  |         files (list): A list of path of stylesheets. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Returns: | 
					
						
							|  |  |  |         dict: A nested dictionary with the structre as | 
					
						
							|  |  |  |         `{'file': {'media-selector': [('selectors',`declarations')]}}` | 
					
						
							|  |  |  |         For example: | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             'datacenterlight/css/landing-page.css':{ | 
					
						
							|  |  |  |                 '(min-width: 768px)': [ | 
					
						
							|  |  |  |                     ('.lead-right', 'text-align: right;'), | 
					
						
							|  |  |  |                 ] | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2018-02-04 03:20:27 +05:30
										 |  |  |     selectors = {} | 
					
						
							|  |  |  |     media_selectors = {} | 
					
						
							| 
									
										
										
										
											2018-02-05 02:47:18 +05:30
										 |  |  |     # get media selectors and other simple declarations | 
					
						
							| 
									
										
										
										
											2018-02-04 03:20:27 +05:30
										 |  |  |     for file in files: | 
					
						
							|  |  |  |         if any(vendor in file for vendor in ['bootstrap', 'font-awesome']): | 
					
						
							|  |  |  |             continue | 
					
						
							|  |  |  |         result = finders.find(file) | 
					
						
							|  |  |  |         if result: | 
					
						
							|  |  |  |             with open(result) as f: | 
					
						
							|  |  |  |                 data = f.read() | 
					
						
							| 
									
										
										
										
											2018-02-05 02:47:18 +05:30
										 |  |  |             media_selectors[file] = string_match_pattern(data, 'css_media') | 
					
						
							|  |  |  |             new_data = string_remove_pattern(data, 'css_media') | 
					
						
							| 
									
										
										
										
											2018-02-08 19:10:37 +05:30
										 |  |  |             default_match = string_match_pattern(new_data, 'css_selector') | 
					
						
							| 
									
										
										
										
											2018-01-30 19:09:55 +05:30
										 |  |  |             selectors[file] = { | 
					
						
							| 
									
										
										
										
											2018-02-08 19:10:37 +05:30
										 |  |  |                 'default': [ | 
					
						
							|  |  |  |                     [' '.join(grp.split()) for grp in m] for m in default_match | 
					
						
							|  |  |  |                 ] | 
					
						
							| 
									
										
										
										
											2018-01-30 19:09:55 +05:30
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2018-02-05 02:47:18 +05:30
										 |  |  |     # get declarations from media queries | 
					
						
							| 
									
										
										
										
											2018-02-04 03:20:27 +05:30
										 |  |  |     for file, match_list in media_selectors.items(): | 
					
						
							|  |  |  |         for match in match_list: | 
					
						
							|  |  |  |             query = match[0] | 
					
						
							|  |  |  |             block_text = ' '.join(match[1].split()) | 
					
						
							|  |  |  |             results = string_match_pattern( | 
					
						
							|  |  |  |                 block_text, 'css_selector' | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |             f_query = ' '.join(query.replace(':', ': ').split()) | 
					
						
							|  |  |  |             if f_query in selectors[file]: | 
					
						
							|  |  |  |                 selectors[file][f_query].extend(results) | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 selectors[file][f_query] = results | 
					
						
							|  |  |  |     return selectors | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def get_selectors_html(files): | 
					
						
							| 
									
										
										
										
											2018-02-05 02:47:18 +05:30
										 |  |  |     """Get `class` and `id` used in html files.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Args: | 
					
						
							|  |  |  |         files (list): A list of html files path. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Returns: | 
					
						
							|  |  |  |         dict: a dictonary of all the classes and ids found in the file, in | 
					
						
							|  |  |  |         `class` and `id` field respectively. | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2018-02-04 03:20:27 +05:30
										 |  |  |     selectors = {} | 
					
						
							|  |  |  |     for file in files: | 
					
						
							|  |  |  |         results = templates_match_pattern(file, ['html_class', 'html_id']) | 
					
						
							| 
									
										
										
										
											2018-02-08 19:10:37 +05:30
										 |  |  |         class_dict = {c: 1 for match in results[0] for c in match.split()} | 
					
						
							| 
									
										
										
										
											2018-02-04 03:20:27 +05:30
										 |  |  |         selectors[file] = { | 
					
						
							| 
									
										
										
										
											2018-02-08 19:10:37 +05:30
										 |  |  |             'classes': list(class_dict.keys()), | 
					
						
							|  |  |  |             'ids': results[1], | 
					
						
							| 
									
										
										
										
											2018-02-04 03:20:27 +05:30
										 |  |  |         } | 
					
						
							|  |  |  |     return selectors | 
					
						
							| 
									
										
										
										
											2018-01-30 19:09:55 +05:30
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def file_match_pattern(file, patterns): | 
					
						
							| 
									
										
										
										
											2018-02-05 02:47:18 +05:30
										 |  |  |     """Match a regex pattern in a file
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Args: | 
					
						
							|  |  |  |         file (str): Complete path of file | 
					
						
							|  |  |  |         patterns (list or str): The pattern(s) to be searched in the file | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Returns: | 
					
						
							|  |  |  |         list: A list of all the matches in the file. Each item is a list of | 
					
						
							|  |  |  |         all the captured groups in the pattern. If multiple patterns are given, | 
					
						
							|  |  |  |         the returned list is a list of such lists. | 
					
						
							|  |  |  |         For example: | 
					
						
							|  |  |  |         [('.lead', 'font-size: 18px;'), ('.btn-lg', 'min-width: 180px;')] | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2018-01-30 19:09:55 +05:30
										 |  |  |     with open(file) as f: | 
					
						
							|  |  |  |         data = f.read() | 
					
						
							| 
									
										
										
										
											2018-02-04 03:20:27 +05:30
										 |  |  |     results = string_match_pattern(data, patterns) | 
					
						
							| 
									
										
										
										
											2018-01-30 19:09:55 +05:30
										 |  |  |     return results | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-04 03:20:27 +05:30
										 |  |  | def string_match_pattern(data, patterns): | 
					
						
							| 
									
										
										
										
											2018-02-05 02:47:18 +05:30
										 |  |  |     """Match a regex pattern in a string
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Args: | 
					
						
							|  |  |  |         data (str): the string to search for the pattern | 
					
						
							|  |  |  |         patterns (list or str): The pattern(s) to be searched in the file | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Returns: | 
					
						
							|  |  |  |         list: A list of all the matches in the string. Each item is a list of | 
					
						
							|  |  |  |         all the captured groups in the pattern. If multiple patterns are given, | 
					
						
							|  |  |  |         the returned list is a list of such lists. | 
					
						
							|  |  |  |         For example: | 
					
						
							|  |  |  |         [('.lead', 'font-size: 18px;'), ('.btn-lg', 'min-width: 180px;')] | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2018-02-04 03:20:27 +05:30
										 |  |  |     if not isinstance(patterns, str): | 
					
						
							|  |  |  |         results = [] | 
					
						
							|  |  |  |         for p in patterns: | 
					
						
							|  |  |  |             re_pattern = re.compile(RE_PATTERNS[p], re.MULTILINE) | 
					
						
							|  |  |  |             results.append(re.findall(re_pattern, data)) | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         re_pattern = re.compile(RE_PATTERNS[patterns], re.MULTILINE) | 
					
						
							|  |  |  |         results = re.findall(re_pattern, data) | 
					
						
							|  |  |  |     return results | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-05 02:47:18 +05:30
										 |  |  | def string_remove_pattern(data, patterns): | 
					
						
							|  |  |  |     """Remove a pattern from a string
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Args: | 
					
						
							|  |  |  |         data (str): the string to search for the patter | 
					
						
							|  |  |  |         patterns (list or str): The pattern(s) to be removed from the file | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Returns: | 
					
						
							| 
									
										
										
										
											2018-02-08 19:10:37 +05:30
										 |  |  |         str: The new string with all instance of matching pattern | 
					
						
							|  |  |  |         removed from it | 
					
						
							| 
									
										
										
										
											2018-02-05 02:47:18 +05:30
										 |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2018-02-04 03:20:27 +05:30
										 |  |  |     if not isinstance(patterns, str): | 
					
						
							|  |  |  |         for p in patterns: | 
					
						
							|  |  |  |             re_pattern = re.compile(RE_PATTERNS[p], re.MULTILINE) | 
					
						
							|  |  |  |             data = re.sub(re_pattern, '', data) | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         re_pattern = re.compile(RE_PATTERNS[patterns], re.MULTILINE) | 
					
						
							|  |  |  |         data = re.sub(re_pattern, '', data) | 
					
						
							|  |  |  |     return data | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-30 19:09:55 +05:30
										 |  |  | def templates_match_pattern(template_name, patterns): | 
					
						
							| 
									
										
										
										
											2018-02-05 02:47:18 +05:30
										 |  |  |     """Match a regex pattern in the first found template file
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Args: | 
					
						
							|  |  |  |         file (str): Path of template file | 
					
						
							|  |  |  |         patterns (list or str): The pattern(s) to be searched in the file | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Returns: | 
					
						
							|  |  |  |         list: A list of all the matches in the file. Each item is a list of | 
					
						
							|  |  |  |         all the captured groups in the pattern. If multiple patterns are given, | 
					
						
							|  |  |  |         the returned list is a list of such lists. | 
					
						
							|  |  |  |         For example: | 
					
						
							|  |  |  |         [('.lead', 'font-size: 18px;'), ('.btn-lg', 'min-width: 180px;')] | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2018-01-30 19:09:55 +05:30
										 |  |  |     t = template.loader.get_template(template_name) | 
					
						
							|  |  |  |     data = t.template.source | 
					
						
							| 
									
										
										
										
											2018-02-04 03:20:27 +05:30
										 |  |  |     results = string_match_pattern(data, patterns) | 
					
						
							| 
									
										
										
										
											2018-01-30 19:09:55 +05:30
										 |  |  |     return results | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-04 03:20:27 +05:30
										 |  |  | def get_css_duplication(css_selectors): | 
					
						
							| 
									
										
										
										
											2018-02-05 02:47:18 +05:30
										 |  |  |     """Get duplicate selectors from the same stylesheet
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Args: | 
					
						
							|  |  |  |         css_selectors (dict): A dictonary containing css selectors from | 
					
						
							|  |  |  |         all the files in the app in the below structure. | 
					
						
							|  |  |  |         `{'file': {'media-selector': [('selectors',`declarations')]}}` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Returns: | 
					
						
							|  |  |  |         dict: A dictonary containing the count of any duplicate selector in | 
					
						
							|  |  |  |         each file. | 
					
						
							|  |  |  |         `{'file': {'media-selector': {'selector': count}}}` | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2018-02-04 03:20:27 +05:30
										 |  |  |     # duplicate css selectors in stylesheets | 
					
						
							| 
									
										
										
										
											2018-02-05 02:47:18 +05:30
										 |  |  |     rule_count = {} | 
					
						
							|  |  |  |     for file, media_selectors in css_selectors.items(): | 
					
						
							|  |  |  |         rule_count[file] = {} | 
					
						
							|  |  |  |         for media, rules in media_selectors.items(): | 
					
						
							|  |  |  |             rules_dict = Counter([rule[0] for rule in rules]) | 
					
						
							|  |  |  |             dup_rules_dict = {k: v for k, v in rules_dict.items() if v > 1} | 
					
						
							|  |  |  |             if dup_rules_dict: | 
					
						
							|  |  |  |                 rule_count[file][media] = dup_rules_dict | 
					
						
							|  |  |  |     return rule_count | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def get_css_unused(css_selectors, html_selectors): | 
					
						
							|  |  |  |     """Get selectors from stylesheets that are not used in any of the html
 | 
					
						
							|  |  |  |     files in which the stylesheet is used. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Args: | 
					
						
							|  |  |  |         css_selectors (dict): A dictonary containing css selectors from | 
					
						
							|  |  |  |         all the files in the app in the below structure. | 
					
						
							|  |  |  |         `{'file': {'media-selector': [('selectors',`declarations')]}}` | 
					
						
							|  |  |  |         html_selectors (dict): A dictonary containing the 'class' and 'id' | 
					
						
							|  |  |  |         declarations from all html files | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2018-02-08 19:10:37 +05:30
										 |  |  |     with open('utils/optimize/test.json', 'w') as f: | 
					
						
							|  |  |  |         json.dump([html_selectors, css_selectors], f, indent=4) | 
					
						
							|  |  |  |     # print(html_selectors, css_selectors) | 
					
						
							| 
									
										
										
										
											2018-02-04 03:20:27 +05:30
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-08 19:10:37 +05:30
										 |  |  | def write_report(all_reports, filename='frontend'): | 
					
						
							| 
									
										
										
										
											2018-02-05 02:47:18 +05:30
										 |  |  |     """Write the generated report to a file for re-use
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Args; | 
					
						
							| 
									
										
										
										
											2018-02-08 19:10:37 +05:30
										 |  |  |         all_reports (dict): A dictonary of report obtained from different tests | 
					
						
							| 
									
										
										
										
											2018-02-05 02:47:18 +05:30
										 |  |  |         filename (str): An optional suffix for the output file | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2018-02-22 14:18:55 +05:30
										 |  |  |     # full_filename = 'utils/optimize/optimize_' + filename + '.html' | 
					
						
							|  |  |  |     # output_file = os.path.join( | 
					
						
							|  |  |  |     #     settings.PROJECT_DIR, full_filename | 
					
						
							|  |  |  |     # ) | 
					
						
							| 
									
										
										
										
											2018-02-08 19:10:37 +05:30
										 |  |  |     with open('utils/optimize/op_frontend.json', 'w') as f: | 
					
						
							|  |  |  |         json.dump(all_reports, f, indent=4) | 
					
						
							| 
									
										
										
										
											2018-02-13 19:03:15 +05:30
										 |  |  |     # with open(output_file, 'w', newline='') as f: | 
					
						
							|  |  |  |     #     f.write( | 
					
						
							|  |  |  |     #         template.loader.render_to_string( | 
					
						
							|  |  |  |     #             'utils/report.html', {'all_reports': all_reports} | 
					
						
							|  |  |  |     #         ) | 
					
						
							|  |  |  |     #     ) | 
					
						
							| 
									
										
										
										
											2018-02-05 02:47:18 +05:30
										 |  |  |         # w = csv.writer(f) | 
					
						
							|  |  |  |         # print(zip_longest(*results)) | 
					
						
							|  |  |  |         # for r in zip_longest(*results): | 
					
						
							|  |  |  |         #     w.writerow(r) | 
					
						
							| 
									
										
										
										
											2018-02-04 03:20:27 +05:30
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-05 02:47:18 +05:30
										 |  |  | # a list of all the html tags (to be moved in a json file) | 
					
						
							| 
									
										
										
										
											2018-01-30 19:09:55 +05:30
										 |  |  | html_tags = [ | 
					
						
							|  |  |  |     "a", | 
					
						
							|  |  |  |     "abbr", | 
					
						
							|  |  |  |     "address", | 
					
						
							|  |  |  |     "article", | 
					
						
							|  |  |  |     "area", | 
					
						
							|  |  |  |     "aside", | 
					
						
							|  |  |  |     "audio", | 
					
						
							|  |  |  |     "b", | 
					
						
							|  |  |  |     "base", | 
					
						
							|  |  |  |     "bdi", | 
					
						
							|  |  |  |     "bdo", | 
					
						
							|  |  |  |     "blockquote", | 
					
						
							|  |  |  |     "body", | 
					
						
							|  |  |  |     "br", | 
					
						
							|  |  |  |     "button", | 
					
						
							|  |  |  |     "canvas", | 
					
						
							|  |  |  |     "caption", | 
					
						
							|  |  |  |     "cite", | 
					
						
							|  |  |  |     "code", | 
					
						
							|  |  |  |     "col", | 
					
						
							|  |  |  |     "colgroup", | 
					
						
							|  |  |  |     "datalist", | 
					
						
							|  |  |  |     "dd", | 
					
						
							|  |  |  |     "del", | 
					
						
							|  |  |  |     "details", | 
					
						
							|  |  |  |     "dfn", | 
					
						
							|  |  |  |     "div", | 
					
						
							|  |  |  |     "dl", | 
					
						
							|  |  |  |     "dt", | 
					
						
							|  |  |  |     "em", | 
					
						
							|  |  |  |     "embed", | 
					
						
							|  |  |  |     "fieldset", | 
					
						
							|  |  |  |     "figcaption", | 
					
						
							|  |  |  |     "figure", | 
					
						
							|  |  |  |     "footer", | 
					
						
							|  |  |  |     "form", | 
					
						
							|  |  |  |     "h1", | 
					
						
							|  |  |  |     "h2", | 
					
						
							|  |  |  |     "h3", | 
					
						
							|  |  |  |     "h4", | 
					
						
							|  |  |  |     "h5", | 
					
						
							|  |  |  |     "h6", | 
					
						
							|  |  |  |     "head", | 
					
						
							|  |  |  |     "header", | 
					
						
							|  |  |  |     "hgroup", | 
					
						
							|  |  |  |     "hr", | 
					
						
							|  |  |  |     "html", | 
					
						
							|  |  |  |     "i", | 
					
						
							|  |  |  |     "iframe", | 
					
						
							|  |  |  |     "img", | 
					
						
							|  |  |  |     "input", | 
					
						
							|  |  |  |     "ins", | 
					
						
							|  |  |  |     "kbd", | 
					
						
							|  |  |  |     "keygen", | 
					
						
							|  |  |  |     "label", | 
					
						
							|  |  |  |     "legend", | 
					
						
							|  |  |  |     "li", | 
					
						
							|  |  |  |     "link", | 
					
						
							|  |  |  |     "map", | 
					
						
							|  |  |  |     "mark", | 
					
						
							|  |  |  |     "menu", | 
					
						
							|  |  |  |     "meta", | 
					
						
							|  |  |  |     "meter", | 
					
						
							|  |  |  |     "nav", | 
					
						
							|  |  |  |     "noscript", | 
					
						
							|  |  |  |     "object", | 
					
						
							|  |  |  |     "ol", | 
					
						
							|  |  |  |     "optgroup", | 
					
						
							|  |  |  |     "option", | 
					
						
							|  |  |  |     "output", | 
					
						
							|  |  |  |     "p", | 
					
						
							|  |  |  |     "param", | 
					
						
							|  |  |  |     "pre", | 
					
						
							|  |  |  |     "progress", | 
					
						
							|  |  |  |     "q", | 
					
						
							|  |  |  |     "rp", | 
					
						
							|  |  |  |     "rt", | 
					
						
							|  |  |  |     "ruby", | 
					
						
							|  |  |  |     "s", | 
					
						
							|  |  |  |     "samp", | 
					
						
							|  |  |  |     "script", | 
					
						
							|  |  |  |     "section", | 
					
						
							|  |  |  |     "select", | 
					
						
							|  |  |  |     "source", | 
					
						
							|  |  |  |     "small", | 
					
						
							|  |  |  |     "span", | 
					
						
							|  |  |  |     "strong", | 
					
						
							|  |  |  |     "style", | 
					
						
							|  |  |  |     "sub", | 
					
						
							|  |  |  |     "summary", | 
					
						
							|  |  |  |     "sup", | 
					
						
							|  |  |  |     "textarea", | 
					
						
							|  |  |  |     "table", | 
					
						
							|  |  |  |     "tbody", | 
					
						
							|  |  |  |     "td", | 
					
						
							|  |  |  |     "tfoot", | 
					
						
							|  |  |  |     "thead", | 
					
						
							|  |  |  |     "th", | 
					
						
							|  |  |  |     "time", | 
					
						
							|  |  |  |     "title", | 
					
						
							|  |  |  |     "tr", | 
					
						
							|  |  |  |     "u", | 
					
						
							|  |  |  |     "ul", | 
					
						
							|  |  |  |     "var", | 
					
						
							|  |  |  |     "video", | 
					
						
							|  |  |  |     "wbr" | 
					
						
							|  |  |  | ] |