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