-
Notifications
You must be signed in to change notification settings - Fork 0
/
CompressHtml.py
190 lines (158 loc) · 6.91 KB
/
CompressHtml.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
Import("env")
#########################################################################################################################################################
#
# CompressHtml
#
# This script is intended for joining a html-file with its js & css files and then compress it and convert it to Arduino (ESP32/ESP8266) PROGMEM format
#
# It will release you from the burden of uploading a FS-Image or convert html back and forth.
#
# Pro`s:
# - One-compile run
# - One source of html, which can be debugged by using live-server, so before even uploading it
# - converges html with its javascript and css files and then compresses it for even faster loading
# - Fast & compact, expect compress rates 2-4x as small, for html/js/css.
# - Also other/multiple files can be converted
# - leaves SPIFFS or LittleFs on the ESP intact, so stored data will not be touched.
# - crude //REMOVE //ENDREMOVE build in for debugging js, this will be stripped by the script.
#
# Cons:
# - takes up progmem space
# - static, not a problem in most cases though
# - little need to setup
#
# Setup:
#
# You`ll need:
# - platformIO
# - this script
# - Compress.yml
# - for serving files, refer to the imagenames of the files in include/appdata.h
#
# Of course you also could do other things with this like a large lookup table.
# Use 'live-server' extension to serve your webpage in your html folder from localhost. With some tricks you can
# call to your ESP-api or ws connection then. This will save you compile and upload time when working on your ESP-s webpage.
import os, gzip, re
from datetime import datetime
try:
import yaml
except ImportError:
env.Execute("$PYTHONEXE -m pip install pyyaml")
try:
import markdown
except ImportError:
env.Execute("$PYTHONEXE -m pip install Markdown")
def GetProgMemString(data, progmemName, addendum = "", comment = ""):
buffer = f' \n\n// {comment}\nconst uint8_t {progmemName + addendum}[] PROGMEM = {{\n'
bytecount = 0
for b in data:
buffer += "0x%02x" % b
if (bytecount != len(data)-1):
buffer += ", "
bytecount +=1
if ((bytecount % 64) == 0):
buffer += "\n"
buffer += "\n};"
return buffer
def SubstituteCssLinks(htmlData, basedir):
cssLinks = re.findall("<link .+?>", htmlData)
for cssLink in cssLinks:
print (f" add css-link: {cssLink}")
cssFilePath = f'{basedir}/' + re.findall("href=\"(.+?)\"",cssLink)[0]
cssFile = open(cssFilePath, "r")
cssFileContent = cssFile.read()
replaceCssLinkBuffer = "\n<style>\n"
replaceCssLinkBuffer += cssFileContent
replaceCssLinkBuffer += "\n</style>\n"
htmlData = htmlData.replace(cssLink, replaceCssLinkBuffer)
return htmlData
def SubstituteJsLinks(htmlData, basedir):
jsLinks = re.findall("<script.+?src=\".+?.js\"></script>", htmlData)
for jsLink in jsLinks:
print (f" add js-link: {jsLink}")
jsFilePath = f'{basedir}/' + re.findall(" src=\"(.+?)\"",jsLink)[0]
jsFile = open(jsFilePath, "r")
jsFileContent = jsFile.read()
replaceJsLinkBuffer = "\n<script>\n"
replaceJsLinkBuffer += jsFileContent
replaceJsLinkBuffer += "\n</script>\n"
htmlData = htmlData.replace(jsLink, replaceJsLinkBuffer)
return htmlData
def RemoveJSRemoves(htmlData):
toRemoves = jsLinks = re.findall("\/\/REMOVE.+?\/\/ENDREMOVE", htmlData, re.DOTALL)
for toRemove in toRemoves:
htmlData = htmlData.replace(toRemove,"")
return htmlData
def ReadAndCreateGetProgMemString(filename):
if (os.path.isfile(filename) == False):
print(f"\nFile: {filename} was not found...\n")
return ""
inFile = open(filename, "rb")
inData = inFile.read()
return GetProgMemString(inData, filename.split("/")[-1].replace(".","_"))
def GetTxtFile(filename):
print (f'processing file: {filename}')
with open(filename, "r") as file:
return file.read()
def GetBinFile(filename):
print (f'processing file: {filename}')
with open(filename, "rb") as file:
return file.read()
def FileHeader():
header = f'// This file is auto-generated by CompressHtml.py @{datetime.now().strftime("%d/%m/%Y %H:%M:%S")}\n'
header += "// Do not edit, since it will be overwritten\n\n"
header += "#include <Arduino.h>\n"
return header
def GzCompressText(data):
outData = gzip.compress(bytes(data, "UTF-8"))
return outData
def GzCompressBin(data):
return gzip.compress(data)
def ProgMemNameFromFileName(filename):
return filename.split("/")[-1].replace(".","_")
def processHtml(filename, basedir):
HtmlData = GetTxtFile(filename)
HtmlData = SubstituteJsLinks(HtmlData, basedir)
HtmlData = RemoveJSRemoves(HtmlData)
HtmlData = SubstituteCssLinks(HtmlData, basedir)
BinGzHtmlData = GzCompressText(HtmlData)
inSize = len(HtmlData)
outSize = len(BinGzHtmlData)
print(f" Size in: {inSize}, size out: {outSize}, ratio: {round(outSize*100/inSize,1)}%.")
return GetProgMemString(BinGzHtmlData,ProgMemNameFromFileName(filename + "_gz"))
def processMD(filename):
mdData = GetTxtFile(filename)
html_md_content = markdown.markdown(mdData)
BinGzMdData = GzCompressText(html_md_content)
inSize = len(html_md_content)
outSize = len(BinGzMdData)
print(f" Size in: {inSize}, size out: {outSize}, ratio: {round(outSize*100/inSize,1)}%.")
return GetProgMemString(BinGzMdData,ProgMemNameFromFileName(filename + "_html_gz"))
def processOtherGz(filename):
BinData = GetBinFile(filename)
BinGzData = GzCompressBin(BinData)
inSize = len(BinData)
outSize = len(BinGzData)
print(f" Size in: {inSize}, size out: {outSize}, ratio: {round(outSize*100/inSize,1)}%.")
return GetProgMemString(BinGzData,ProgMemNameFromFileName(filename + "_gz"))
def ProcessOther(filename):
BinData = GetBinFile(filename)
print(f" Size in: {len(BinData)} (no compression).")
return GetProgMemString(BinData,ProgMemNameFromFileName(filename))
# main:
print("\n**************** Compress.py start ****************\n")
with open('Compress.yml', 'r') as file:
ymlConfig = yaml.safe_load(file)
print("Config: Compress.yml was red.")
headerFileData = FileHeader()
for file in ymlConfig["HTML"]:
headerFileData += processHtml(ymlConfig["SOURCEDIR"] + "/" + file, ymlConfig["SOURCEDIR"])
for file in ymlConfig["MARKDOWN"]:
headerFileData += processMD(file)
for file in ymlConfig["OTHERSGZ"]:
headerFileData += processOtherGz(ymlConfig["SOURCEDIR"] + "/" + file)
for file in ymlConfig["OTHERS"]:
headerFileData += ProcessOther(ymlConfig["SOURCEDIR"] + "/" + file)
with open(ymlConfig["DESTINATIONFILE"], "w") as outfile:
outfile.write(headerFileData)
print("\n**************** Compress.py end ****************\n")