xref: /openbmc/phosphor-led-manager/scripts/parse_led.py (revision 6fd9eea5fdb3fe8c1aea2d549af553b11dd2be4f)
1 #!/usr/bin/env python3
2 import argparse
3 import os
4 import re
5 import sys
6 
7 import yaml
8 from inflection import underscore
9 
10 
11 def 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 
43 def 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 
52 def 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 
68 def 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 
81 def 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 
99 def led_action_literal(action):
100     if action == "":
101         return "std::nullopt"
102 
103     return "phosphor::led::Layout::Action::" + str(action)
104 
105 
106 def 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 
146 def 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 
190 def 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 
207 if __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