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