optimize script
This commit is contained in:
		
					parent
					
						
							
								789d8a408f
							
						
					
				
			
			
				commit
				
					
						9d196416e2
					
				
			
		
					 1 changed files with 287 additions and 0 deletions
				
			
		
							
								
								
									
										287
									
								
								utils/management/commands/optimize_frontend.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										287
									
								
								utils/management/commands/optimize_frontend.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,287 @@ | |||
| import csv | ||||
| import logging | ||||
| import os | ||||
| import re | ||||
| from collections import Counter, OrderedDict | ||||
| from itertools import zip_longest | ||||
| 
 | ||||
| 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)', | ||||
|     'css_selector': ( | ||||
|         '^\s*([.#\[:_A-Za-z][.#\[\]\(\)=:+~\-_A-Za-z0-9\s>,]*)' | ||||
|         '{([\s\S]*?)}' | ||||
|     ), | ||||
|     'html_class': 'class=[\'\"]([a-zA-Z0-9-_\s]*)', | ||||
|     'html_id': 'id=[\'\"]([a-zA-Z0-9-_]*)' | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| class Command(BaseCommand): | ||||
|     help = 'Finds and fixes unused css styles in the templates' | ||||
|     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', | ||||
|             help='optimize only css rules in each file' | ||||
|         ) | ||||
| 
 | ||||
|     def handle(self, *args, **options): | ||||
|         apps_list = options['apps'] | ||||
|         for app in apps_list: | ||||
|             if options['css']: | ||||
|                 self.optimize_css(app) | ||||
|             else: | ||||
|                 self.optimize_all(app) | ||||
| 
 | ||||
|     def optimize_css(self, app_name): | ||||
|         # get html and css files used in the app | ||||
|         files = self.get_files(app_name) | ||||
|         # get_selectors_from_css | ||||
|         css_selectors = self.get_selectors_css(files['style']) | ||||
|         # get_selectors_from_html | ||||
|         html_selectors = self.get_selectors_html(files['html']) | ||||
| 
 | ||||
|         # duplicate css selectors in stylesheets | ||||
|         for file, selectors in css_selectors.items(): | ||||
|             count = {} | ||||
|             for selector in selectors: | ||||
|                 if selector[0] in count: | ||||
|                     count[selector[0]] += 1 | ||||
|                     print(file, selector[0], count[selector[0]]) | ||||
|                 else: | ||||
|                     count[selector[0]] = 1 | ||||
|             # print(count) | ||||
| 
 | ||||
|     def get_files(self, app_name): | ||||
|         # 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'])[0] | ||||
|         # 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: | ||||
|                 # a dict containing 'html' and 'css' files | ||||
|                 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) | ||||
|         } | ||||
|         print(result) | ||||
|         return result | ||||
| 
 | ||||
|     def get_selectors_css(self, files): | ||||
|         selectors = {} | ||||
|         for file in files: | ||||
|             if any(vendor in file for vendor in ['bootstrap', 'font-awesome']): | ||||
|                 continue | ||||
|             result = finders.find(file) | ||||
|             if result: | ||||
|                 selectors[file] = file_match_pattern( | ||||
|                     result, ['css_selector'] | ||||
|                 )[0] | ||||
|         return selectors | ||||
| 
 | ||||
|     def get_selectors_html(self, files): | ||||
|         selectors = {} | ||||
|         for file in files: | ||||
|             results = templates_match_pattern(file, ['html_class', 'html_id']) | ||||
|             selectors[file] = { | ||||
|                 'class': results[0], | ||||
|                 'id': results[0], | ||||
|             } | ||||
|         return selectors | ||||
| 
 | ||||
|     def selectors_css(self, results, filename='frontend'): | ||||
|         full_filename = '../optimize_' + filename + '.csv' | ||||
|         output_file = os.path.join( | ||||
|             settings.PROJECT_DIR, full_filename | ||||
|         ) | ||||
|         with open(output_file, 'w', newline='') as f: | ||||
|             w = csv.writer(f) | ||||
|             # print(zip_longest(*results)) | ||||
|             for r in zip_longest(*results): | ||||
|                 # print(r) | ||||
|                 w.writerow(r) | ||||
| 
 | ||||
| 
 | ||||
| def file_match_pattern(file, patterns): | ||||
|     results = [] | ||||
|     with open(file) as f: | ||||
|         data = f.read() | ||||
|     for p in patterns: | ||||
|         results.append( | ||||
|             re.findall(re.compile(RE_PATTERNS[p], re.MULTILINE), data) | ||||
|         ) | ||||
|     return results | ||||
| 
 | ||||
| 
 | ||||
| def templates_match_pattern(template_name, patterns): | ||||
|     t = template.loader.get_template(template_name) | ||||
|     data = t.template.source | ||||
|     results = [] | ||||
|     for p in patterns: | ||||
|         results.append( | ||||
|             re.findall(re.compile(RE_PATTERNS[p], re.MULTILINE), data) | ||||
|         ) | ||||
|     return results | ||||
| 
 | ||||
| 
 | ||||
| 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" | ||||
| ] | ||||
| 
 | ||||
| exempt_classes = [ | ||||
|     "active", | ||||
| ] | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue