generate_param: add a means to generate param_table_gen.c from the docs
[sfrench/samba-autobuild/.git] / script / generate_param.py
1 # Unix SMB/CIFS implementation.
2 # Copyright (C) 2014 Catalyst.Net Ltd
3 #
4 # Auto generate param_functions.c
5 #
6 #   ** NOTE! The following LGPL license applies to the ldb
7 #   ** library. This does NOT imply that all of Samba is released
8 #   ** under the LGPL
9 #
10 #  This library is free software; you can redistribute it and/or
11 #  modify it under the terms of the GNU Lesser General Public
12 #  License as published by the Free Software Foundation; either
13 #  version 3 of the License, or (at your option) any later version.
14 #
15 #  This library is distributed in the hope that it will be useful,
16 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
17 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18 #  Lesser General Public License for more details.
19 #
20 #  You should have received a copy of the GNU Lesser General Public
21 #  License along with this library; if not, see <http://www.gnu.org/licenses/>.
22 #
23
24 import errno
25 import os
26 import re
27 import subprocess
28 import xml.etree.ElementTree as ET
29 import sys
30 import optparse
31
32 # parse command line arguments
33 parser = optparse.OptionParser()
34 parser.add_option("-f", "--file", dest="filename",
35                   help="input file", metavar="FILE")
36 parser.add_option("-o", "--output", dest="output",
37                   help='output file', metavar="FILE")
38 parser.add_option("--mode", type="choice", metavar="<FUNCTIONS|S3PROTO|LIBPROTO|PARAMDEFS|PARAMTABLE>",
39                  choices=["FUNCTIONS", "S3PROTO", "LIBPROTO", "PARAMDEFS", "PARAMTABLE"], default="FUNCTIONS")
40 parser.add_option("--scope", metavar="<GLOBAL|LOCAL>",
41                   choices = ["GLOBAL", "LOCAL"], default="GLOBAL")
42
43 (options, args) = parser.parse_args()
44
45 if options.filename is None:
46     parser.error("No input file specified")
47 if options.output is None:
48     parser.error("No output file specified")
49
50 def iterate_all(path):
51     """Iterate and yield all the parameters. 
52
53     :param path: path to parameters xml file
54     """
55
56     try:
57         p = open(path, 'r')
58     except IOError, e:
59         raise Exception("Error opening parameters file")
60     out = p.read()
61
62     # parse the parameters xml file
63     root = ET.fromstring(out)
64     for parameter in root:
65         name = parameter.attrib.get("name")
66         param_type = parameter.attrib.get("type")
67         context = parameter.attrib.get("context")
68         func = parameter.attrib.get("function")
69         synonym = parameter.attrib.get("synonym")
70         removed = parameter.attrib.get("removed")
71         generated = parameter.attrib.get("generated_function")
72         handler = parameter.attrib.get("handler")
73         enumlist = parameter.attrib.get("enumlist")
74         deprecated = parameter.attrib.get("deprecated")
75         synonyms = parameter.findall('synonym')
76
77         if removed == "1":
78             continue
79
80         constant = parameter.attrib.get("constant")
81         parm = parameter.attrib.get("parm")
82         if name is None or param_type is None or context is None:
83             raise Exception("Error parsing parameter: " + name)
84         if func is None:
85             func = name.replace(" ", "_").lower()
86         if enumlist is None:
87             enumlist = "NULL"
88         if handler is None:
89             handler = "NULL"
90         yield {'name': name,
91                'type': param_type,
92                'context': context,
93                'function': func,
94                'constant': (constant == '1'),
95                'parm': (parm == '1'),
96                'synonym' : synonym,
97                'generated' : generated,
98                'enumlist' : enumlist,
99                'handler' : handler,
100                'deprecated' : deprecated,
101                'synonyms' : synonyms }
102
103 # map doc attributes to a section of the generated function
104 context_dict = {"G": "_GLOBAL", "S": "_LOCAL"}
105 param_type_dict = {
106                     "boolean"      : "_BOOL",
107                     "list"         : "_LIST",
108                     "string"       : "_STRING",
109                     "integer"      : "_INTEGER",
110                     "enum"         : "_INTEGER",
111                     "char"         : "_CHAR",
112                     "boolean-auto" : "_INTEGER",
113                     "cmdlist"      : "_LIST",
114                     "bytes"        : "_INTEGER",
115                     "octal"        : "_INTEGER",
116                     "ustring"      : "_STRING",
117                   }
118
119 def generate_functions(path_in, path_out):
120     f = open(path_out, 'w')
121     try:
122        f.write('/* This file was automatically generated by generate_param.py. DO NOT EDIT */\n\n')
123        for parameter in iterate_all(options.filename):
124             # filter out parameteric options
125             if ':' in parameter['name']:
126                 continue
127             if parameter['synonym'] == "1":
128                 continue
129             if parameter['generated'] == "0":
130                 continue
131
132             output_string = "FN"
133             temp = context_dict.get(parameter['context'])
134             if temp is None: 
135                 raise Exception(parameter['name'] + " has an invalid context " + parameter['context'])
136             output_string += temp
137             if parameter['constant']:
138                 output_string += "_CONST"
139             if parameter['parm']: 
140                 output_string += "_PARM"
141             temp = param_type_dict.get(parameter['type'])
142             if temp is None:
143                 raise Exception(parameter['name'] + " has an invalid param type " + parameter['type'])
144             output_string += temp
145             f.write(output_string + "(" + parameter['function'] +", " + parameter['function'] + ')\n')
146     finally:
147         f.close()
148
149 mapping = {
150             'boolean'      : 'bool ',
151             'string'       : 'char *',
152             'integer'      : 'int ',
153             'char'         : 'char ',
154             'list'         : 'const char **',
155             'enum'         : 'int ',
156             'boolean-auto' : 'int ',
157             'cmdlist'      : 'const char **',
158             'bytes'        : 'int ',
159             'octal'        : 'int ',
160             'ustring'      : 'char *',
161           }
162
163 def make_s3_param_proto(path_in, path_out):
164     file_out = open(path_out, 'w')
165     try:
166         file_out.write('/* This file was automatically generated by generate_param.py. DO NOT EDIT */\n\n')
167         header = get_header(path_out)
168         file_out.write("#ifndef %s\n" % header)
169         file_out.write("#define %s\n\n" % header)
170         for parameter in iterate_all(path_in):
171             # filter out parameteric options
172             if ':' in parameter['name']:
173                 continue
174             if parameter['synonym'] == "1":
175                 continue
176             if parameter['generated'] == "0":
177                 continue
178
179             output_string = ""
180             if parameter['constant']:
181                 output_string += 'const '
182             param_type = mapping.get(parameter['type'])
183             if param_type is None:
184                raise Exception(parameter['name'] + " has an invalid context " + parameter['context'])
185             output_string += param_type
186             output_string += "lp_%s" % parameter['function']
187
188             param = None
189             if parameter['parm']:
190                 param = "const struct share_params *p"
191             else:
192                 param = "int"
193
194             if parameter['type'] == 'string' and not parameter['constant']:
195                 if parameter['context'] == 'G':
196                     output_string += '(TALLOC_CTX *ctx);\n'
197                 elif parameter['context'] == 'S':
198                     output_string += '(TALLOC_CTX *ctx, %s);\n' % param
199                 else:
200                     raise Exception(parameter['name'] + " has an invalid param type " + parameter['type'])
201             else:
202                 if parameter['context'] == 'G':
203                     output_string += '(void);\n'
204                 elif parameter['context'] == 'S':
205                     output_string += '(%s);\n' % param
206                 else:
207                     raise Exception(parameter['name'] + " has an invalid param type " + parameter['type'])
208
209             file_out.write(output_string)
210
211         file_out.write("\n#endif /* %s */\n\n" % header)
212     finally:
213         file_out.close()
214
215
216 def make_lib_proto(path_in, path_out):
217     file_out = open(path_out, 'w')
218     try:
219         file_out.write('/* This file was automatically generated by generate_param.py. DO NOT EDIT */\n\n')
220         for parameter in iterate_all(path_in):
221             # filter out parameteric options
222             if ':' in parameter['name']:
223                 continue
224             if parameter['synonym'] == "1":
225                 continue
226             if parameter['generated'] == "0":
227                 continue
228
229             output_string = ""
230             if parameter['constant']:
231                 output_string += 'const '
232             param_type = mapping.get(parameter['type'])
233             if param_type is None:
234                raise Exception(parameter['name'] + " has an invalid context " + parameter['context'])
235             output_string += param_type
236
237             output_string += "lpcfg_%s" % parameter['function']
238
239             if parameter['type'] == 'string' and not parameter['constant']:
240                 if parameter['context'] == 'G':
241                     output_string += '(struct loadparm_context *, TALLOC_CTX *ctx);\n'
242                 elif parameter['context'] == 'S':
243                     output_string += '(struct loadparm_service *, struct loadparm_service *, TALLOC_CTX *ctx);\n'
244                 else:
245                     raise Exception(parameter['name'] + " has an invalid param type " + parameter['type'])
246             else:
247                 if parameter['context'] == 'G':
248                     output_string += '(struct loadparm_context *);\n'
249                 elif parameter['context'] == 'S':
250                     output_string += '(struct loadparm_service *, struct loadparm_service *);\n'
251                 else:
252                     raise Exception(parameter['name'] + " has an invalid param type " + parameter['type'])
253
254             
255             file_out.write(output_string)
256     finally:
257         file_out.close()
258
259 def get_header(path):
260     header = os.path.basename(path).upper()
261     header = header.replace(".", "_").replace("\\", "_").replace("-", "_")
262     return "__%s__" % header
263
264 def make_param_defs(path_in, path_out, scope):
265     file_out = open(path_out, 'w')
266     try:
267         file_out.write('/* This file was automatically generated by generate_param.py. DO NOT EDIT */\n\n')
268         header = get_header(path_out)
269         file_out.write("#ifndef %s\n" % header)
270         file_out.write("#define %s\n\n" % header)
271         if scope == "GLOBAL": 
272             file_out.write("/**\n")
273             file_out.write(" * This structure describes global (ie., server-wide) parameters.\n")
274             file_out.write(" */\n")
275             file_out.write("struct loadparm_global \n")
276             file_out.write("{\n")
277             file_out.write("\tTALLOC_CTX *ctx; /* Context for talloced members */\n")
278         elif scope == "LOCAL":
279             file_out.write("/**\n")
280             file_out.write(" * This structure describes a single service.\n")
281             file_out.write(" */\n")
282             file_out.write("struct loadparm_service \n")
283             file_out.write("{\n")
284             file_out.write("\tbool   autoloaded;\n")
285  
286         for parameter in iterate_all(path_in):
287             # filter out parameteric options
288             if ':' in parameter['name']:
289                 continue
290             if parameter['synonym'] == "1":
291                 continue
292
293             if (scope == "GLOBAL" and parameter['context'] != "G" or
294                 scope == "LOCAL" and parameter['context'] != "S"):
295                 continue
296
297             output_string = "\t"
298             param_type = mapping.get(parameter['type'])
299             if param_type is None:
300                raise Exception(parameter['name'] + " has an invalid context " + parameter['context'])
301             output_string += param_type
302
303             output_string += "  %s;\n" % parameter['function']
304             file_out.write(output_string)
305
306         file_out.write("LOADPARM_EXTRA_%sS\n" % scope)
307         file_out.write("};\n")
308         file_out.write("\n#endif /* %s */\n\n" % header)
309     finally:
310         file_out.close()
311
312 type_dict = {
313               "boolean"      : "P_BOOL",
314               "boolean-rev"  : "P_BOOLREV",
315               "boolean-auto" : "P_ENUM",
316               "list"         : "P_LIST",
317               "string"       : "P_STRING",
318               "integer"      : "P_INTEGER",
319               "enum"         : "P_ENUM",
320               "char"         : "P_CHAR",
321               "cmdlist"      : "P_CMDLIST",
322               "bytes"        : "P_BYTES",
323               "octal"        : "P_OCTAL",
324               "ustring"      : "P_USTRING",
325             }
326
327 def make_param_table(path_in, path_out):
328     file_out = open(path_out, 'w')
329     try:
330         file_out.write('/* This file was automatically generated by generate_param.py. DO NOT EDIT */\n\n')
331         header = get_header(path_out)
332         file_out.write("#ifndef %s\n" % header)
333         file_out.write("#define %s\n\n" % header)
334
335         file_out.write("struct parm_struct parm_table[] = {\n")
336
337         for parameter in iterate_all(path_in):
338             # filter out parameteric options
339             if ':' in parameter['name']:
340                 continue
341             if parameter['context'] == 'G':
342                 p_class = "P_GLOBAL"
343             else:
344                 p_class = "P_LOCAL"
345
346             p_type = type_dict.get(parameter['type'])
347
348             if parameter['context'] == 'G':
349                 temp = "GLOBAL"
350             else:
351                 temp = "LOCAL"
352             offset = "%s_VAR(%s)" % (temp, parameter['function'])
353
354             enumlist = parameter['enumlist']
355             handler = parameter['handler']
356             synonym = parameter['synonym']
357             deprecated = parameter['deprecated']
358             flags_list = []
359             if synonym == "1":
360                 flags_list.append("FLAG_SYNONYM")
361             if deprecated == "1":
362                 flags_list.append("FLAG_DEPRECATED")
363             flags = "|".join(flags_list)
364             synonyms = parameter['synonyms']
365
366             file_out.write("\t{\n")
367             file_out.write("\t\t.label\t\t= \"%s\",\n" % parameter['name'])
368             file_out.write("\t\t.type\t\t= %s,\n" % p_type)
369             file_out.write("\t\t.p_class\t= %s,\n" % p_class)
370             file_out.write("\t\t.offset\t\t= %s,\n" % offset)
371             file_out.write("\t\t.special\t= %s,\n" % handler)
372             file_out.write("\t\t.enum_list\t= %s,\n" % enumlist)
373             if flags != "":
374                 file_out.write("\t\t.flags\t\t= %s,\n" % flags)
375             file_out.write("\t},\n")
376
377             if synonyms is not None:
378                 # for synonyms, we only list the synonym flag:
379                 flags = "FLAG_SYNONYM"
380                 for syn in synonyms:
381                     file_out.write("\t{\n")
382                     file_out.write("\t\t.label\t\t= \"%s\",\n" % syn.text)
383                     file_out.write("\t\t.type\t\t= %s,\n" % p_type)
384                     file_out.write("\t\t.p_class\t= %s,\n" % p_class)
385                     file_out.write("\t\t.offset\t\t= %s,\n" % offset)
386                     file_out.write("\t\t.special\t= %s,\n" % handler)
387                     file_out.write("\t\t.enum_list\t= %s,\n" % enumlist)
388                     if flags != "":
389                         file_out.write("\t\t.flags\t\t= %s,\n" % flags)
390                     file_out.write("\t},\n")
391
392         file_out.write("\n\t{NULL,  P_BOOL,  P_NONE,  0,  NULL,  NULL,  0}\n");
393         file_out.write("};\n")
394         file_out.write("\n#endif /* %s */\n\n" % header)
395     finally:
396         file_out.close()
397
398 if options.mode == 'FUNCTIONS':
399     generate_functions(options.filename, options.output)
400 elif options.mode == 'S3PROTO':
401     make_s3_param_proto(options.filename, options.output)
402 elif options.mode == 'LIBPROTO':
403     make_lib_proto(options.filename, options.output)
404 elif options.mode == 'PARAMDEFS':
405     make_param_defs(options.filename, options.output, options.scope)
406 elif options.mode == 'PARAMTABLE':
407     make_param_table(options.filename, options.output)