Newer
Older
import os
import shutil
import re
import subprocess
import argparse

Hartung, Michael
committed
def find_nth(haystack, needle, n):
start = haystack.find(needle)
while start >= 0 and n > 1:
start = haystack.find(needle, start+len(needle))
n -= 1
return start
class ParserHTML:
PREFIX = 'drugstone-plugin-'
CLASSSEARCHPATTERN = 'class="'
ICONCLASSSEARCHPATTERN = 'classString="'
IDSEARCHPATTERN = 'id="'
NGCLASSSEARCHPATTERN = '[ngClass]="'
PARSEDFILENEDING = '.parsed'
NGCLASSINDIVIDUALPATTERN = '[class.'
TOOLTIPCLASSPATTERN = '[tooltipStyleClass]="'
def prefixNgIndivClassStrings(self, line, classStart):
line = line[:classStart] + self.PREFIX + line[classStart:]
def prefixNgClassStrings(self, line, classStart):
start = False
classIndices = []

Hartung, Michael
committed
stringComparison = False
seenQuestionmark = False
for i, c in enumerate(line[classStart:], classStart):

Hartung, Michael
committed
if c == "'" and not stringComparison:
if not start:
classIndices.append(i+1)
start = True
else:
start = False

Hartung, Michael
committed
elif c == '}' or c =='"':

Hartung, Michael
committed
elif c == ':' and not seenQuestionmark:
stringComparison = True
elif c == ',':
stringComparison = False
seenQuestionmark = False
elif c == '?':
# if we see a ?, the following : does not implicate a string comparison but a case separation
seenQuestionmark = True
for i, start in enumerate(classIndices):
start += i * len(self.PREFIX)
line = line[:start] + self.PREFIX + line[start:]
return line
def findClassStrings(self, line, classStart):
classIndices = []
start = classStart
lastWasCurl = False
lastWasClosingCurl = False
inVariable = False
for i, c in enumerate(line[classStart:], classStart):
if (c == ' ' or c == '"') and not inVariable and i > start:
classIndices.append((start, i))
start = i + 1
elif c == '{':
if lastWasCurl:
inVariable = True
lastWasCurl = True
elif c == '}':
if lastWasClosingCurl:
inVariable = False
lastWasClosingCurl = True
else:
lastWasCurl = False
lastWasClosingCurl = False
if c == '"':
return classIndices, i
return classIndices, len(line)
def updateClassStrings(self, line, classIndices, classStart, classEnd, iTagOpen):
renamedClassList = []
for start, end in classIndices:
classString = line[start:end]
if classString.startswith('ng-') or (iTagOpen and classString.startswith('fa') or classString.startswith('{')):
renamedClassList.append(classString)
continue
renamedClass = self.prefixClass(classString)
renamedClassList.append(renamedClass)
return self.updateClasses(line, renamedClassList, classStart, classEnd)
def prefixClass(self, classString):
return self.PREFIX + classString
def prefixtooltipStrings(self, line, tooltipClassStart):
subline = line[tooltipClassStart:]
start = subline.find("'")+1
end = find_nth(subline, "'", 2)
classStringList = subline[start:end].split(' ')
classStringList = [self.prefixClass(x) for x in classStringList]
line = line[:tooltipClassStart+start] + ' '.join(classStringList) + line[tooltipClassStart+end:]
return line
def updateClasses(self, line, renamedClassList, classStart, classEnd):
renamedClassString = ' '.join(renamedClassList)
return line[:classStart] + renamedClassString + line[classEnd:]
def parseHtml(self, path):
newLines = []
with open(path) as f:
content = ''
# remove linebreaks in tags

Hartung, Michael
committed
stringOpen = False
for line in f:
if not len(line.strip()):
continue
# line.count('"') % 2 --> opened but not closed like [ngClass]="
if line.count('"') % 2 and not line.strip().endswith('>'):
content += line.strip() + ' '

Hartung, Michael
committed
stringOpen = not stringOpen

Hartung, Michael
committed
if stringOpen:
# no new line
content += line.strip() + ' '
else:
# new line

Hartung, Michael
committed
iTagOpen = False
for line in content.split('\n'):
line = line.strip()
if '<i' in line:
iTagOpen = True
classStart = line.find(self.CLASSSEARCHPATTERN)
if classStart > -1:
classStart += len(self.CLASSSEARCHPATTERN)
classIndices, classEnd = self.findClassStrings(line, classStart)
line = self.updateClassStrings(line, classIndices, classStart, classEnd, iTagOpen)
iconClassStart = line.find(self.ICONCLASSSEARCHPATTERN)
if iconClassStart > -1:
iconClassStart += len(self.ICONCLASSSEARCHPATTERN)
classIndices, classEnd = self.findClassStrings(line, iconClassStart)
line = self.updateClassStrings(line, classIndices, iconClassStart, classEnd, iTagOpen)
ngClassStart = line.find(self.NGCLASSSEARCHPATTERN)
if ngClassStart > -1:
ngClassStart += len(self.NGCLASSSEARCHPATTERN)
line = self.prefixNgClassStrings(line, ngClassStart)
ngClassIndivStart = line.find(self.NGCLASSINDIVIDUALPATTERN)
if ngClassIndivStart > -1:
ngClassIndivStart += len(self.NGCLASSINDIVIDUALPATTERN)
# exclude .fa classes
if not line[ngClassIndivStart:].startswith('fa-'):
line = self.prefixNgIndivClassStrings(line, ngClassIndivStart)
tooltipClassStart = line.find(self.TOOLTIPCLASSPATTERN)
if tooltipClassStart > -1:
tooltipClassStart += len(self.TOOLTIPCLASSPATTERN)
line = self.prefixtooltipStrings(line, tooltipClassStart)
if self.IDSEARCHPATTERN in line:
line = line.replace(self.IDSEARCHPATTERN, self.IDSEARCHPATTERN + self.PREFIX)
newLines.append(line)
if '</i' in line:
iTagOpen = False
return '\n'.join(newLines)
def write(self, path, html):
writePath = path + self.PARSEDFILENEDING
with open(writePath, "w") as f:
print(html, file=f)
# overwrite file
os.rename(writePath, path)
def parseDirectory(self, directory):
for root, dirs, files in os.walk(directory):
for file in files:
if file.endswith(".component.html"):
if ('fa-icons' in file):
continue
html = self.parseHtml(path)
self.write(path, html)
class ParserJS:
PREFIX = 'drugstone-plugin-'
PARSEDFILENEDING = '.parsed'
DIR = 'src/'
ELEMENTBYIDSTRING = 'document.getElementById('

Hartung, Michael
committed
def findId(self, line):
start = line.find(self.ELEMENTBYIDSTRING) + len(self.ELEMENTBYIDSTRING)+1
return start
def replaceElementById(self, line):

Hartung, Michael
committed
start = self.findId(line)
line = line[:start] + self.PREFIX + line[start:]
return line
def parseJS(self, path):
newLines = []
with open(path) as f:
for line in f:
if self.ELEMENTBYIDSTRING in line:
line = self.replaceElementById(line)
newLines.append(line)
return '\n'.join(newLines)
def write(self, path, html):
writePath = path + self.PARSEDFILENEDING
with open(writePath, "w") as f:
print(html, file=f)
# overwrite file
os.rename(writePath, path)
def parseDirectory(self, directory):
for root, dirs, files in os.walk(directory):
for file in files:
if file.endswith(".component.ts"):
path = os.path.join(root, file)
html = self.parseJS(path)
self.write(path, html)
class ParserCSS:
PREFIXCLASS = '.drugstone-plugin-'
PREFIXID = '#drugstone-plugin-'
PARSEDFILENEDING = '.parsed'
DIR = 'src/'
def __init__(self):
pass
def charIsNumber(self, x):
try:
int(x)
return True
except:
return False
def findClassEnding(self, line, start):
start += 1
for i, c in enumerate(line[start:], start):
if c == '.' or c == ' ' or c == '{' or c ==',':
return i
return len(line)
def findPotentialIdEnding(self, line, start):
# can be id or hexacode color
start += 1
for i, c in enumerate(line[start:], start):
if c == ';' or c == '}' or c == '!' or c == ' ' or c == ',':
return i
return len(line)
def prefixClasses(self, classListString):
classListStringList = classListString.split('.')
classListStringList = [x for x in classListStringList if len(x)]
classListStringList = [self.PREFIXCLASS + x if not (x.startswith('ng-') or x.startswith('p-') or x.startswith('pi-') or x.startswith('drugstone-plugin-') or x.startswith('fa-')) else '.' + x for x in classListStringList]
return '.'.join(classListStringList)
def prefixId(self, classListString):
return classListString.replace('#', self.PREFIXID)
def parseCSS(self, path):
newLines = []
with open(path) as f:
for line in f:
# leading white spaces are necessary for sass
leadingWhiteSaces = len(line) - len(line.lstrip())
line = line.strip()
if line.startswith('//'):
# skip comments
continue
if not len(line):
# skip empty lines as empty lines in the beginning of .sass files cause errors
continue
if line.startswith('@import') or line.startswith('@forward') or line.startswith('@error') or line.startswith('@mixin') or line.startswith('@content') or line.startswith('src'):
line = leadingWhiteSaces*' ' + line
newLines.append(line)
# leave imports untouched
continue
i = 0
while i < len(line):
c = line[i]
if c == '.':
# i+1 < len(line) is necessary for online comments that end with a dot
if not i+1 < len(line):
i += 1
continue
if self.charIsNumber(line[i+1]):
i += 1
continue
classListEnd = self.findClassEnding(line, i)
classListString = line[i:classListEnd]
renamedClasses = self.prefixClasses(classListString)
line = line[:i] + renamedClasses + line[classListEnd:]
i = classListEnd + renamedClasses.count(self.PREFIXCLASS)*len(self.PREFIXCLASS) - 2
elif c == '#':
if i+1 < len(line) and line[i+1] == '{':
i += 1
continue
# test if string is hexacode color
end = self.findPotentialIdEnding(line, i)
# end > -1 for color in comment
if end > -1 and re.search(r'^#(?:[0-9a-fA-F]{3}){1,2}$', line[i:end]):
i = end
continue
classListEnd = self.findClassEnding(line, i)
classListString = line[i:classListEnd]
renamedClasses = self.prefixId(classListString)
line = line[:i] + renamedClasses + line[classListEnd:]
i += classListEnd + len(self.PREFIXID) - 2
i += 1
# add white spaces
line = leadingWhiteSaces*' ' + line
newLines.append(line)
return '\n'.join(newLines)
def write(self, path, html):
writePath = path + self.PARSEDFILENEDING
with open(writePath, "w") as f:
print(html, file=f)
# overwrite file
os.rename(writePath, path)
def parseDirectory(self, directory):
for root, dirs, files in os.walk(directory):
for file in files:
if file.endswith(".scss") or file.endswith(".css") or file.endswith(".sass"):
path = os.path.join(root, file)
# skip ng select classes
if '@ng-select' in path or '-no-prefix.scss' in path:
scss = self.parseCSS(path)
self.write(path, scss)
def __init__(self, buildPath):
self.buildPath = buildPath
def buildDevDir(self):
shutil.copytree('src', os.path.join(self.buildPath, 'src'))
shutil.copytree('node_modules', os.path.join(self.buildPath, 'node_modules'))
def parseApp(self):
ParserHTML().parseDirectory('src/app/')
ParserCSS().parseDirectory('src/')
ParserCSS().parseDirectory('node_modules/')
ParserJS().parseDirectory('src/app/')
def cleanup(self):
shutil.rmtree('src')
shutil.copytree(os.path.join(self.buildPath, 'src'), 'src')
shutil.rmtree('node_modules')
shutil.rmtree(self.buildPath)
subprocess.run(['npm', 'i'])
ORIGDIR = 'original'
def parse():
print('Starting parsing...')
buildManager = BuildManager(ORIGDIR)
try:
buildManager.buildDevDir()
buildManager.parseApp()
except:
raise Exception('ERROR: CSS prefix script failed.')
print('Parsing done!')
print('Starting cleanup...')
buildManager = BuildManager(ORIGDIR)
buildManager.cleanup()
print('Cleanup done!')
parser = argparse.ArgumentParser()
parser.add_argument("-s", "--stage", help = "Stage of building. Either 'parse' or 'cleanup'.")
if __name__ == '__main__':
args = parser.parse_args()
if not args.stage:
raise Exception('Value for --stage is missing.')
try:
parse()
except:
# in case it fails, try again after running a cleanup
cleanup()
try:
parse()
except:
cleanup()
elif args.stage == 'cleanup':
cleanup()
else:
raise Exception(f'ERROR: Unknown argument for --stage: {args.stage}. Should be "parse" or "stage."')