From b7b6b8a3cda94f023f9947a4fa2d44e2f7e3ca15 Mon Sep 17 00:00:00 2001 From: Dave Kliczbor Date: Sat, 1 Dec 2018 19:23:46 +0100 Subject: [PATCH] Add code comments in prepare_static_ui_sources.py --- tools/prepare_static_ui_sources.py | 48 +++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/tools/prepare_static_ui_sources.py b/tools/prepare_static_ui_sources.py index 7bf2813..18335e0 100755 --- a/tools/prepare_static_ui_sources.py +++ b/tools/prepare_static_ui_sources.py @@ -1,11 +1,13 @@ #!/usr/bin/env python3 +### import always available modules import sys import os.path import argparse import re from glob import glob +### import not always installed modules missing = [] try: from jsmin import jsmin as jsminify @@ -24,6 +26,7 @@ try: except ModuleNotFoundError as e: missing.append(e) if len(missing) > 0: + # ERROR: at least one module is missing for m in missing: print("Cannot find module '%s'." % m.name) print("Can't find %s required python module%s. Please install %s. If you're not sure how, a web search" % (len(missing), "s" if len(missing) > 1 else "", "them" if len(missing) > 1 else "it")) @@ -32,6 +35,8 @@ if len(missing) > 0: print("Here's the long documentation: https://packaging.python.org/tutorials/installing-packages/") sys.exit(0) + +### String template for C header files TARGET_TEMPLATE = '''const char {constant}[] PROGMEM = R"=====( {minidata} )====="; @@ -39,7 +44,9 @@ TARGET_TEMPLATE = '''const char {constant}[] PROGMEM = R"=====( const uint8_t {constant}_GZIP[{gziplen}] PROGMEM = {{ {gzipdata} }}; ''' + def parse_arguments(args=None): + """ Command line argument parser definitions """ parser = argparse.ArgumentParser( description="Prepares ESPUI header files by minifying and gzipping HTML, JS and CSS source files.") parser.add_argument("--auto", "--all", "-a", dest="auto", action="store_true", @@ -58,6 +65,20 @@ def parse_arguments(args=None): return args def get_context(infile, outfile): + """ This function creates a 'context object': a dictionary containing all the data needed. + Dictionary members: + infile: Full path to the input file or directory as given or autodetected + indir: Full path to the infile parent. + dir: Full path to the input directory tree (indir or parent of indir). + name: Filename of infile excluding extension (i.e. filename up until the first dot) + type: Lowercase extension of infile; one of: html, js, css. + outfile: Full path to the output file or directory as given or autodetected. + outdir: Full path to output directory. + outfilename: Filename of outfile. + minifile: Full path and filename of the intermediary minified file. + constant: C header file constant name derived from infile. + If infile == minifile, the input file already is minified (contains ".min.") + """ infile = os.path.realpath(infile) dir, name, type = (os.path.basename(os.path.dirname(infile)), os.path.basename(infile).split(os.path.extsep)[0], os.path.basename(infile).split(os.path.extsep)[-1] ) type = type.strip(".").lower() @@ -80,6 +101,11 @@ def get_context(infile, outfile): return locals() def perform_gzip(c): + """ Performs GZIP on c['minidata']. + The returned context object will contain additional fields: + gzipdata: Comma-separated string of decimal byte values representing gzipped data. + gziplen: Count of decimal byte values in gzipdata. + """ compressed = gzip.compress(bytes(c['minidata'], 'utf-8')) c['gzipdata'] = ','.join([ str(b) for b in compressed ]) c['gziplen'] = len(compressed) @@ -87,6 +113,9 @@ def perform_gzip(c): return c def perform_minify(c): + """ Performs minification on c['infile']. + The returned context object contains the additional field minidata: A string of minified file contents. + """ with open(c['infile']) as infile: minifier = { 'css': cssminify, @@ -95,19 +124,27 @@ def perform_minify(c): }.get(c['type']) or htmlminify print(" Using %s minifier" % c['type']) c['minidata'] = minifier(infile.read()) - return perform_gzip(c) + return c def process_file(infile, outdir, storemini=True): + """ Processes one file """ print("Processing file %s" % infile) + # Evaluate file and target context c = get_context(infile, outdir) + # Minify file data c = perform_minify(c) + # Gzip minified data + c = perform_gzip(c) + if storemini: + # Write intermediary minified file if c['infile'] == c['minifile']: print(" Original file is already minified, refusing to overwrite it") else: print(" Writing minified file %s" % c['minifile']) with open(c['minifile'], 'w+') as minifile: minifile.write(c['minidata']) + # Write minified and gzipped data to C header file with open(c['outfile'], 'w+') as outfile: print(" Using C constant names %s and %s_GZIP" % (c['constant'], c['constant'])) print(" Writing C header file %s" % c['outfile']) @@ -117,6 +154,10 @@ def filenamefilter(pattern, strings): return filter(re.compile(pattern).search, strings) def process_dir(sourcedir, outdir, recursive=True, storemini=True): + """ Processes a directory tree, recursively. Calls process_file on each HTML/CSS/JS file found. + Skips intermediary minified files. Standalone minified files (i.e. files containing ".min." that + do not have a full version) are processed without minifying again. + """ pattern = r'/*\.(css|js|htm|html)$' files = glob(sourcedir + "/**/*", recursive=True)+glob(sourcedir + "/*") if recursive else glob(sourcedir + "/*") files = filenamefilter(pattern, files) @@ -127,6 +168,7 @@ def process_dir(sourcedir, outdir, recursive=True, storemini=True): process_file(f, outdir, storemini) def check_args(args): + """ Checks argumental sanity and exits if the arguments are insane. """ abort = 0 if not os.path.exists(args.sources): print("ERROR: Source %s does not exist" % args.sources) @@ -142,8 +184,12 @@ def check_args(args): sys.exit(abort) def main(args): + """ main entry point. """ + # default source if not given: realpath(../examples/gui/data) args.sources = os.path.realpath(args.sources or os.sep.join((os.path.dirname(os.path.realpath(__file__)), "..", "examples", "gui", "data"))) + # default target if not given: realpath(../src) args.target = os.path.realpath(args.target or os.sep.join((os.path.dirname(os.path.realpath(__file__)), "..", "src"))) + # check arguments check_args(args) if os.path.isfile(args.sources): print("Source %s is a file, will process one file only." % args.sources)