xref: /openbmc/phosphor-led-manager/scripts/parse_led.py (revision 6fd9eea5fdb3fe8c1aea2d549af553b11dd2be4f)
1#!/usr/bin/env python3
2import argparse
3import os
4import re
5import sys
6
7import yaml
8from inflection import underscore
9
10
11def is_dbus_safe(name: str) -> bool:
12    """
13    Check if a string is safe for use on D-Bus.
14
15    Args:
16        name (str): The input string.
17
18    Returns:
19        bool: True if the string is safe for D-Bus, False otherwise.
20    """
21    if not name:
22        return False  # Empty names are not allowed
23
24    # Check for invalid characters
25    if not re.match(r"^[a-zA-Z0-9_]+$", name):
26        return False
27
28    # Check for leading or trailing dots/underscores
29    if name[0] in "._" or name[-1] in "._":
30        return False
31
32    # Check for consecutive dots or underscores
33    if ".." in name or "__" in name:
34        return False
35
36    # Check length restriction
37    if len(name) > 255:
38        return False
39
40    return True
41
42
43def convert_dbus_name_error(name: str) -> str:
44
45    if not is_dbus_safe(name):
46        print("WARNING: '" + name + "' is not dbus safe", file=sys.stderr)
47        raise ValueError("WARNING: '" + name + "' is not dbus safe")
48
49    return name
50
51
52def convert_dbus_name_warn(name: str) -> str:
53
54    if not is_dbus_safe(name):
55        print("WARNING: '" + name + "' is not dbus safe", file=sys.stderr)
56
57    result = underscore(name)
58
59    if name != result:
60        print(
61            "WARNING: converted '" + name + "' to '" + result + "'",
62            file=sys.stderr,
63        )
64
65    return result
66
67
68def config_error(ofile, led_name, group_name, message):
69    ofile.close()
70    os.remove(ofile.name)
71    raise ValueError(
72        "Invalid Configuration for LED ["
73        + led_name
74        + "] in Group ["
75        + group_name
76        + "]: "
77        + message
78    )
79
80
81def check_led_priority(led_name, group, value, priority_dict):
82
83    if led_name in priority_dict:
84        if value != priority_dict[led_name]:
85            # Priority for a particular LED needs to stay SAME
86            # across all groups
87            config_error(
88                ofile,
89                led_name,
90                group,
91                "Priority is NOT same across all groups",
92            )
93    else:
94        priority_dict[led_name] = value
95
96    return 0
97
98
99def led_action_literal(action):
100    if action == "":
101        return "std::nullopt"
102
103    return "phosphor::led::Layout::Action::" + str(action)
104
105
106def generate_file_single_led(
107    ifile, led_name, list_dict, priority_dict, ofile, has_group_priority, group
108):
109
110    has_led_priority = "Priority" in list_dict
111
112    if has_group_priority and has_led_priority:
113        config_error(
114            ofile,
115            led_name,
116            group,
117            "cannot mix group priority with led priority",
118        )
119
120    if (not has_group_priority) and (not has_led_priority):
121        config_error(
122            ofile, led_name, group, "no group priority or led priority defined"
123        )
124
125    led_priority = list_dict.get("Priority", "")
126
127    if has_led_priority:
128        check_led_priority(led_name, group, led_priority, priority_dict)
129
130    action = led_action_literal(list_dict.get("Action", "Off"))
131    dutyOn = str(list_dict.get("DutyOn", 50))
132    period = str(list_dict.get("Period", 0))
133    priority = led_action_literal(led_priority)
134
135    ofile.write('        {"' + convert_dbus_name_warn(led_name) + '",')
136    ofile.write(action + ",")
137    ofile.write(dutyOn + ",")
138    ofile.write(period + ",")
139    ofile.write(priority + ",")
140
141    ofile.write("},\n")
142
143    return 0
144
145
146def generate_file_single_group(ifile, group, priority_dict, ofile):
147    # This section generates an std::unordered_map of LedGroupNames to
148    # std::set of LEDs containing the name and properties
149    led_dict = ifile[group]
150
151    group_priority = 0
152    has_group_priority = led_dict and "Priority" in led_dict
153
154    if has_group_priority:
155        group_priority = led_dict["Priority"]
156        # we do not want to enumerate this as a led group
157        del led_dict["Priority"]
158
159    ofile.write(
160        '   {"'
161        + "/xyz/openbmc_project/led/groups/"
162        + convert_dbus_name_error(group)
163        + '"'
164        + ",{ "
165        + str(group_priority)
166        + ",\n"
167        + "{\n"
168    )
169
170    # Some LED groups could be empty
171    if not led_dict:
172        led_dict = {}
173
174    for led_name, list_dict in list(led_dict.items()):
175        generate_file_single_led(
176            ifile,
177            led_name,
178            list_dict,
179            priority_dict,
180            ofile,
181            has_group_priority,
182            group,
183        )
184
185    ofile.write("   }}},\n")
186
187    return 0
188
189
190def generate_file(ifile, ofile):
191    # Dictionary having [Name:Priority]
192    priority_dict = {}
193
194    ofile.write("/* !!! WARNING: This is a GENERATED Code..")
195    ofile.write("Please do NOT Edit !!! */\n\n")
196
197    ofile.write("static const phosphor::led::GroupMap")
198    ofile.write(" systemLedMap = {\n\n")
199
200    for group in list(ifile.keys()):
201        generate_file_single_group(ifile, group, priority_dict, ofile)
202    ofile.write("};\n")
203
204    return 0
205
206
207if __name__ == "__main__":
208    script_dir = os.path.dirname(os.path.realpath(__file__))
209    parser = argparse.ArgumentParser()
210    parser.add_argument(
211        "-f", "--filename", default="led.yaml", help="Input File Name"
212    )
213    parser.add_argument(
214        "-l",
215        "--output-filename",
216        dest="outputfilename",
217        default="led-gen.hpp",
218        help="Output File Name",
219    )
220    parser.add_argument(
221        "-i",
222        "--input-dir",
223        dest="inputdir",
224        default=script_dir,
225        help="Input directory",
226    )
227    parser.add_argument(
228        "-o",
229        "--output-dir",
230        dest="outputdir",
231        default=".",
232        help="Output directory.",
233    )
234
235    args = parser.parse_args()
236
237    # Default to the one that is in the current.
238    yaml_dir = script_dir
239    yaml_file = os.path.join(yaml_dir, "led.yaml")
240
241    if args.inputdir:
242        yaml_dir = args.inputdir
243
244    if args.filename:
245        yaml_file = os.path.join(yaml_dir, args.filename)
246
247    with open(yaml_file, "r") as f:
248        ifile = yaml.safe_load(f)
249
250    with open(os.path.join(args.outputdir, args.outputfilename), "w") as ofile:
251        generate_file(ifile, ofile)
252