1#!/usr/bin/env python
2
3import sys, re, getopt
4
5class Menusystem:
6
7   types = {"run"      : "OPT_RUN",
8            "inactive" : "OPT_INACTIVE",
9            "checkbox" : "OPT_CHECKBOX",
10            "radiomenu": "OPT_RADIOMENU",
11            "sep"      : "OPT_SEP",
12            "invisible": "OPT_INVISIBLE",
13            "radioitem": "OPT_RADIOITEM",
14            "exitmenu" : "OPT_EXITMENU",
15            "login"    : "login", # special type
16            "submenu"  : "OPT_SUBMENU"}
17
18   entry_init = { "item" : "",
19                  "info" : "",
20                  "data" : "",
21                  "ipappend" : 0, # flag to send in case of PXELINUX
22                  "helpid" : 65535, # 0xFFFF
23                  "shortcut":"-1",
24                  "state"  : 0, # initial state of checkboxes
25                  "argsmenu": "", # name of menu containing arguments
26                  "perms"  : "", # permission required to execute this entry
27                  "_updated" : None, # has this dictionary been updated
28                  "type" : "run" }
29
30   menu_init = {  "title" : "",
31                  "row" : "0xFF", # let system decide position
32                  "col" : "0xFF",
33                  "_updated" : None,
34                  "name" : "" }
35
36   system_init ={ "videomode" : "0xFF",
37                  "title" : "Menu System",
38                  "top" : "1",
39                  "left" : "1" ,
40                  "bot" : "21",
41                  "right":"79",
42                  "helpdir" : "/isolinux/help",
43                  "pwdfile" : "",
44                  "pwdrow"  : "23",
45                  "editrow" : "23",
46                  "skipcondn"  : "0",
47                  "skipcmd" : ".exit",
48                  "startfile": "",
49                  "onerrorcmd":".repeat",
50                  "exitcmd"  : ".exit",
51                  "exitcmdroot"  : "",
52                  "timeout"  : "600",
53                  "timeoutcmd":".beep",
54                  "totaltimeout" : "0",
55                  "totaltimeoutcmd" : ".wait"
56                 }
57
58   shift_flags = { "alt"  : "ALT_PRESSED",
59                   "ctrl" : "CTRL_PRESSED",
60                   "shift": "SHIFT_PRESSED",
61                   "caps" : "CAPSLOCK_ON",
62                   "num"  : "NUMLOCK_ON",
63                   "ins"  : "INSERT_ON"
64                 }
65
66   reqd_templates = ["item","login","menu","system"]
67
68   def __init__(self,template):
69       self.state = "system"
70       self.code_template_filename = template
71       self.menus = []
72       self.init_entry()
73       self.init_menu()
74       self.init_system()
75       self.vtypes = " OR ".join(list(self.types.keys()))
76       self.vattrs = " OR ".join([x for x in list(self.entry.keys()) if x[0] != "_"])
77       self.mattrs = " OR ".join([x for x in list(self.menu.keys()) if x[0] != "_"])
78
79   def init_entry(self):
80       self.entry = self.entry_init.copy()
81
82   def init_menu(self):
83       self.menu = self.menu_init.copy()
84
85   def init_system(self):
86       self.system = self.system_init.copy()
87
88   def add_menu(self,name):
89       self.add_item()
90       self.init_menu()
91       self.menu["name"] = name
92       self.menu["_updated"] = 1
93       self.menus.append( (self.menu,[]) )
94
95   def add_item(self):
96       if self.menu["_updated"]: # menu details have changed
97          self.menus[-1][0].update(self.menu)
98          self.init_menu()
99       if self.entry["_updated"]:
100          if not self.entry["info"]:
101             self.entry["info"] = self.entry["data"]
102          if not self.menus:
103             print("Error before line %d" % self.lineno)
104             print("REASON: menu must be declared before a menu item is declared")
105             sys.exit(1)
106          self.menus[-1][1].append(self.entry)
107       self.init_entry()
108
109   def set_item(self,name,value):
110       if name not in self.entry:
111          msg = ["Unknown attribute %s in line %d" % (name,self.lineno)]
112          msg.append("REASON: Attribute must be one of %s" % self.vattrs)
113          return "\n".join(msg)
114       if name=="type" and value not in self.types:
115          msg = [ "Unrecognized type %s in line %d" % (value,self.lineno)]
116          msg.append("REASON: Valid types are %s" % self.vtypes)
117          return "\n".join(msg)
118       if name=="shortcut":
119          if (value != "-1") and not re.match("^[A-Za-z0-9]$",value):
120             msg = [ "Invalid shortcut char '%s' in line %d" % (value,self.lineno) ]
121             msg.append("REASON: Valid values are [A-Za-z0-9]")
122             return "\n".join(msg)
123          elif value != "-1": value = "'%s'" % value
124       elif name in ["state","helpid","ipappend"]:
125          try:
126              value = int(value)
127          except:
128              return "Value of %s in line %d must be an integer" % (name,self.lineno)
129       self.entry[name] = value
130       self.entry["_updated"] = 1
131       return ""
132
133   def set_menu(self,name,value):
134       if name not in self.menu:
135          return "Error: Unknown keyword %s" % name
136       self.menu[name] = value
137       self.menu["_updated"] = 1
138       return ""
139
140   def set_system(self,name,value):
141       if name not in self.system:
142          return "Error: Unknown keyword %s" % name
143       if name == "skipcondn":
144          try: # is skipcondn a number?
145             a = int(value)
146          except: # it is a "-" delimited sequence
147             value = value.lower()
148             parts = [ self.shift_flags.get(x.strip(),None) for x in value.split("-") ]
149             self.system["skipcondn"] = " | ".join([_f for _f in parts if _f])
150       else:
151          self.system[name] = value
152
153   def set(self,name,value):
154       # remove quotes if given
155       if (value[0] == value[-1]) and (value[0] in ['"',"'"]): # remove quotes
156          value = value[1:-1]
157       if self.state == "system":
158          err = self.set_system(name,value)
159          if not err: return
160       if self.state == "menu":
161          err = self.set_menu(name,value)
162          # change state to entry it menu returns error
163          if err:
164             err = None
165             self.state = "item"
166       if self.state == "item":
167          err = self.set_item(name,value)
168
169       if not err: return
170
171       # all errors so return item's error message
172       print(err)
173       sys.exit(1)
174
175   def print_entry(self,entry,fd):
176       entry["type"] = self.types[entry["type"]]
177       if entry["type"] == "login": #special type
178          fd.write(self.templates["login"] % entry)
179       else:
180          fd.write(self.templates["item"] % entry)
181
182   def print_menu(self,menu,fd):
183       if menu["name"] == "main": self.foundmain = 1
184       fd.write(self.templates["menu"] % menu)
185       if (menu["row"] != "0xFF") or (menu["col"] != "0xFF"):
186          fd.write('  set_menu_pos(%(row)s,%(col)s);\n' % menu)
187
188
189   def output(self,filename):
190       curr_template = None
191       contents = []
192       self.templates = {}
193       regbeg = re.compile(r"^--(?P<name>[a-z]+) BEGINS?--\n$")
194       regend = re.compile(r"^--[a-z]+ ENDS?--\n$")
195       ifd = open(self.code_template_filename,"r")
196       for line in ifd.readlines():
197           b = regbeg.match(line)
198           e = regend.match(line)
199           if e: # end of template
200              if curr_template:
201                 self.templates[curr_template] = "".join(contents)
202              curr_template = None
203              continue
204           if b:
205              curr_template = b.group("name")
206              contents = []
207              continue
208           if not curr_template: continue # lines between templates are ignored
209           contents.append(line)
210       ifd.close()
211
212       missing = None
213       for x in self.reqd_templates:
214           if x not in self.templates: missing = x
215       if missing:
216           print("Template %s required but not defined in %s" % (missing,self.code_template_filename))
217
218       if filename == "-":
219          fd = sys.stdout
220       else: fd = open(filename,"w")
221       self.foundmain = None
222       fd.write(self.templates["header"])
223       fd.write(self.templates["system"] % self.system)
224       for (menu,items) in self.menus:
225           self.print_menu(menu,fd)
226           for entry in items: self.print_entry(entry,fd)
227       fd.write(self.templates["footer"])
228       fd.close()
229       if not self.foundmain:
230          print("main menu not found")
231          print(self.menus)
232          sys.exit(1)
233
234   def input(self,filename):
235       if filename == "-":
236          fd = sys.stdin
237       else: fd = open(filename,"r")
238       self.lineno = 0
239       self.state = "system"
240       for line in fd.readlines():
241         self.lineno = self.lineno + 1
242         if line and line[-1] in ["\r","\n"]: line = line[:-1]
243         if line and line[-1] in ["\r","\n"]: line = line[:-1]
244         line = line.strip()
245         if line and line[0] in ["#",";"]: continue
246
247         try:
248           # blank line -> starting a new entry
249           if not line:
250              if self.state == "item": self.add_item()
251              continue
252
253           # starting a new section?
254           if line[0] == "[" and line[-1] == "]":
255              self.state = "menu"
256              self.add_menu(line[1:-1])
257              continue
258
259           # add property of current entry
260           pos = line.find("=") # find the first = in string
261           if pos < 0:
262              print("Syntax error in line %d" % self.lineno)
263              print("REASON: non-section lines must be of the form ATTRIBUTE=VALUE")
264              sys.exit(1)
265           attr = line[:pos].strip().lower()
266           value = line[pos+1:].strip()
267           self.set(attr,value)
268         except:
269            print("Error while parsing line %d: %s" % (self.lineno,line))
270            raise
271       fd.close()
272       self.add_item()
273
274def usage():
275    print(sys.argv[0]," [options]")
276    print("--input=<file>    is the name of the .menu file declaring the menu structure")
277    print("--output=<file>   is the name of generated C source")
278    print("--template=<file> is the name of template to be used")
279    print()
280    print("input and output default to - (stdin and stdout respectively)")
281    print("template defaults to adv_menu.tpl")
282    sys.exit(1)
283
284def main():
285    tfile = "adv_menu.tpl"
286    ifile = "-"
287    ofile = "-"
288    opts,args = getopt.getopt(sys.argv[1:], "hi:o:t:",["input=","output=","template=","help"])
289    if args:
290       print("Unknown options %s" % args)
291       usage()
292    for o,a in opts:
293        if o in ["-i","--input"]:
294           ifile = a
295        elif o in ["-o", "--output"]:
296           ofile = a
297        elif o in ["-t","--template"]:
298           tfile = a
299        elif o in ["-h","--help"]:
300           usage()
301
302    inst = Menusystem(tfile)
303    inst.input(ifile)
304    inst.output(ofile)
305
306if __name__ == "__main__":
307   main()
308