1#!/usr/bin/env python3
2import os
3import re
4import argparse
5from collections import defaultdict
6
7
8style_properties_type = {
9    "LV_STYLE_BG_COLOR": "LV_PROPERTY_TYPE_COLOR",
10    "LV_STYLE_BG_GRAD_COLOR": "LV_PROPERTY_TYPE_COLOR",
11    "LV_STYLE_BG_IMAGE_SRC": "LV_PROPERTY_TYPE_IMGSRC",
12    "LV_STYLE_BG_IMAGE_RECOLOR": "LV_PROPERTY_TYPE_COLOR",
13    "LV_STYLE_BORDER_COLOR": "LV_PROPERTY_TYPE_COLOR",
14    "LV_STYLE_OUTLINE_COLOR": "LV_PROPERTY_TYPE_COLOR",
15    "LV_STYLE_SHADOW_COLOR": "LV_PROPERTY_TYPE_COLOR",
16    "LV_STYLE_IMAGE_RECOLOR": "LV_PROPERTY_TYPE_COLOR",
17    "LV_STYLE_ARCH_IMAGE_SRC": "LV_PROPERTY_TYPE_IMGSRC",
18    "LV_STYLE_ARCH_COLOR": "LV_PROPERTY_TYPE_COLOR",
19    "LV_STYLE_TEXT_COLOR": "LV_PROPERTY_TYPE_COLOR",
20    "LV_STYLE_TEXT_FONT": "LV_PROPERTY_TYPE_FONT",
21    "LV_STYLE_LINE_COLOR": "LV_PROPERTY_TYPE_COLOR",
22}
23
24
25class Property:
26    def __init__(self, widget, name, type, index, id):
27        self.widget = widget
28        self.name = name
29        self.type = type
30        self.index = index
31        self.id = id
32
33
34def find_headers(directory):
35    if os.path.isfile(directory):
36        yield directory
37        return
38
39    for root, dirs, files in os.walk(directory):
40        for file in files:
41            if file.endswith('.h'):
42                yield os.path.join(root, file)
43
44
45def read_widget_properties(directory):
46
47    def match_properties(file_path):
48        pattern = r'^\s*LV_PROPERTY_ID2?\((\w+),\s*(\w+),\s*(\w+)(,\s*(\w+))?,\s*(\d+)\)'
49        with open(file_path, 'r') as file:
50            for line in file.readlines():
51                match = re.match(pattern, line)
52                if match:
53                    id = f"LV_PROPERTY_{match.group(1).upper()}_{match.group(2).upper()}"
54                    yield Property(
55                        match.group(1).lower(),
56                        match.group(2).lower(), match.group(3), match.group(4),
57                        id)
58
59    def match_styles(file_path):
60        pattern = r'^\s+LV_STYLE_(\w+)\s*=\s*(\d+),'
61        with open(file_path, 'r') as file:
62            for line in file.readlines():
63                match = re.match(pattern, line)
64                if match:
65                    name = match.group(1).upper()
66                    id = f"LV_PROPERTY_STYLE_{name}"
67                    yield Property("style",
68                                   match.group(1).lower(), "style",
69                                   match.group(2), id)
70
71    properties_by_widget = defaultdict(list)
72    for file_path in find_headers(directory):
73        for property in match_properties(file_path):
74            properties_by_widget[property.widget].append(property)
75
76        for property in match_styles(file_path):
77            properties_by_widget[property.widget].append(property)
78
79        for widget, properties in properties_by_widget.items():
80            # sort properties by property name
81            properties.sort(key=lambda x: x.name)
82            properties_by_widget[widget] = properties
83
84    return properties_by_widget
85
86
87def write_widget_properties(output, properties_by_widget):
88    # Open header file for update.
89    with open(f'{output}/lv_obj_property_names.h', "w") as header:
90        header.write(f'''
91/**
92 * @file lv_obj_property_names.h
93 * GENERATED FILE, DO NOT EDIT IT!
94 */
95#ifndef LV_OBJ_PROPERTY_NAMES_H
96#define LV_OBJ_PROPERTY_NAMES_H
97
98#include "../../misc/lv_types.h"
99
100#if LV_USE_OBJ_PROPERTY && LV_USE_OBJ_PROPERTY_NAME
101
102''')
103
104        for widget in sorted(properties_by_widget.keys()):
105            properties = properties_by_widget[widget]
106            file_name = f'lv_{widget}_properties.c'
107            output_file = f'{output}/{file_name}'
108
109            count = len(properties)
110            if widget == 'style':
111                include = "lv_style_properties.h"
112                guard = None
113            elif widget == "obj":
114                include = "../../core/lv_obj.h"
115                guard = None
116            else:
117                include = f'../{widget}/lv_{widget}.h'
118                guard = f"#if LV_USE_{widget.upper()}"
119
120            with open(output_file, 'w') as f:
121                f.write(f'''
122/**
123 * GENERATED FILE, DO NOT EDIT IT!
124 * @file {file_name}
125 */
126
127#include "{include}"
128
129#if LV_USE_OBJ_PROPERTY && LV_USE_OBJ_PROPERTY_NAME
130
131{guard if guard else ""}
132/**
133 * {widget.capitalize() + ' widget' if widget != 'style' else 'Style'} property names, name must be in order.
134 * Generated code from properties.py
135 */
136/* *INDENT-OFF* */
137const lv_property_name_t lv_{widget}_property_names[{count}] = {{
138''')
139
140                for property in properties:
141                    name = property.name
142                    name_str = '"' + name + '",'
143                    f.write(f"    {{{name_str :25} {property.id},}},\n")
144
145                f.write('};\n')
146                if guard:
147                    f.write(f"#endif /*LV_USE_{widget.upper()}*/\n\n")
148                f.write("/* *INDENT-ON* */\n")
149                f.write('#endif\n')
150            header.write(
151                f'    extern const lv_property_name_t lv_{widget}_property_names[{count}];\n'
152            )
153        header.write('#endif\n')
154        header.write('#endif\n')
155
156
157def write_style_header(output, properties_by_widget):
158    properties = properties_by_widget['style']
159
160    output_file = f'{output}/lv_style_properties.h'
161
162    with open(output_file, 'w') as f:
163        f.write(f'''
164/**
165 * GENERATED FILE, DO NOT EDIT IT!
166 * @file lv_style_properties.h
167 */
168#ifndef LV_STYLE_PROPERTIES_H
169#define LV_STYLE_PROPERTIES_H
170
171#include "../../core/lv_obj_property.h"
172#if LV_USE_OBJ_PROPERTY
173
174
175/* *INDENT-OFF* */
176enum {{
177''')
178
179        for property in properties:
180            name = property.name
181            id_type = style_properties_type.get(f"LV_STYLE_{name.upper()}",
182                                                "LV_PROPERTY_TYPE_INT")
183            f.write(
184                f"    LV_PROPERTY_ID(STYLE, {name.upper() + ',' :25} {id_type+',' :28} LV_STYLE_{name.upper()}),\n"
185            )
186
187        f.write('};\n\n')
188        f.write('#endif\n')
189        f.write('#endif\n')
190
191
192def main(directory, output):
193    property = read_widget_properties(directory)
194    write_widget_properties(output, property)
195    write_style_header(output, property)
196
197
198if __name__ == "__main__":
199    parser = argparse.ArgumentParser(
200        description='Search files and filter lines.')
201    parser.add_argument('-d', '--directory',
202                        help='Directory to lvgl root path')
203    parser.add_argument(
204        '-o', '--output', help='Folders to write generated properties for all widgets.')
205    args = parser.parse_args()
206
207    # default directory is the lvgl root path of where this script sits
208    if args.directory is None:
209        args.directory = os.path.join(os.path.dirname(__file__), "../")
210
211    if args.output is None:
212        args.output = os.path.join(args.directory, "src/widgets/property/")
213
214    # create output directory if it doesn't exist
215    os.makedirs(args.output, exist_ok=True)
216
217    main(args.directory, args.output)
218