Based on the answer by @Pucor I created this function to automate the process and avoid having to create two files:
def load_style(path: str):
invalid_syntax = Exception('Invalid syntax')
stylesheet = ''
with open(path, 'r') as file:
# Block posidion code:
# Its used to make sure the syntax is consistent with the rest of the file (I know there are better ways)
root_block = 0 # 0= No parsing done yet
variables = dict[str, str]() # Create an empty dictionary to store the variables
for line in file:
if ':root' in line:
root_block = 1 # 1= :root label found, expecting opening bracket
if '{' in line:
if root_block == 1:
root_block = 2 # 2= opening brakcet found, the block is "safe" to read
continue
else:
raise invalid_syntax
if '}' in line: # If the closing bracket is found, check the opening bracket has been found first
if root_block != 2: # If not the case, throw an error
raise invalid_syntax
# Else we reached the end of the block and the rsulting stylesheet is the rest of the file from this point
else:
stylesheet = file.read()
break
# If its safe to read, then split the key, values by a colon
if root_block == 2:
split = line.split(':')
# Using match/case since its cool; store the variable in the dict
match split:
case [key, value]:
variables[key.strip()] = value.strip()
case _:
raise invalid_syntax
# This pattern uses a logical OR to capture the words in the brackets
# The dictionary keys are sorted in reversed since the regex engine only captures the first occurence
# If p.e. "primary" is set before "primary-dark" then it will only capture "primary"
pattern = re.compile('@(' + '|'.join(sorted(variables, key= len, reverse=True)).strip('|') + ')')
# Replace each match with the value of the variables dictionary (group 0 is a match that contains the @)
stylesheet = pattern.sub(lambda match: variables[match.group(1)], stylesheet)
return stylesheet
Please note that the funciton is not bulletproof since it depends on a procedurally made regex capturing group at the end that I havent tested in depth..
Usage:
In your stylesheet.qss:
:root{
primary: #1a1f1f
primary-dark: #101010
primary-light: #353535
secondary: #2a82da
secondary-dark: #163f68
}
QWidget{
border: 0px;
background-color: @primary;
color: white;
}
The parser will start by storing all the "variables" inside the :root block and then replace each referenced variable starting with a @ in the rest of the file.
To use it in your application:
style = load_style('stylesheet.qss')
app.setStyleSheet(style)
I understand the code is messy and unreadable