1#!/usr/bin/env python 2 3import argparse 4import logging 5import json 6import os 7from pprint import pprint 8import re 9import sys 10 11def touch(filename): 12 open(filename, 'a').close() 13 14class daemonName: 15 def __init__( self ): 16 table = { 17 "Director": "Dir", 18 "StorageDaemon": "Sd", 19 "FileDaemon": "Fd" 20 } 21 22 @staticmethod 23 def getLong( string ): 24 if string.lower() == "director" or string.lower() == "dir": 25 return "Director" 26 elif string.lower() == "storagedaemon" or string.lower() == "sd": 27 return "StorageDaemon" 28 elif string.lower() == "filedaemon" or string.lower() == "fd": 29 return "FileDaemon" 30 31 @staticmethod 32 def getShort( string ): 33 if string.lower() == "bareos-dir" or string.lower() == "director" or string.lower() == "dir": 34 return "Dir" 35 elif string.lower() == "bareos-sd" or string.lower() == "storagedaemon" or string.lower() == "sd": 36 return "Sd" 37 elif string.lower() == "bareos-fd" or string.lower() == "filedaemon" or string.lower() == "fd": 38 return "Fd" 39 elif string.lower() == "bareos-console" or string.lower() == "bconsole" or string.lower() == "con": 40 return "Console" 41 elif string.lower() == "bareos-tray-monitor": 42 return "Console" 43 44 @staticmethod 45 def getLowShort(string): 46 return daemonName.getShort(string).lower() 47 48 49class BareosConfigurationSchema: 50 def __init__( self, json ): 51 self.format_version_min = 2 52 self.logger = logging.getLogger() 53 self.json = json 54 try: 55 self.format_version = json['format-version'] 56 except KeyError as e: 57 raise 58 logger.info( "format-version: " + str(self.format_version) ) 59 if self.format_version < self.format_version_min: 60 raise RuntimeError( "format-version is " + str(self.format_version) + ". Required: >= " + str(self.format_version_min) ) 61 62 def open(self, filename = None, mode = 'r'): 63 if filename: 64 self.out=open( filename, mode ) 65 else: 66 self.out=sys.stdout 67 68 def close(self): 69 if self.out != sys.stdout: 70 self.out.close() 71 72 def getDaemons(self): 73 return sorted(filter( None, self.json["resource"].keys())) 74 75 def getDatatypes(self): 76 try: 77 return sorted(filter( None, self.json["datatype"].keys()) ) 78 except KeyError: 79 return 80 81 def convertCamelCase2Spaces(self, valueCC): 82 s1 = re.sub('([a-z0-9])([A-Z])', r'\1 \2', valueCC) 83 result=[] 84 for token in s1.split(' '): 85 u = token.upper() 86 # TODO: add LAN 87 if u in [ "ACL", "CA", "CN", "DB", "DH", "FD", "LMDB", "NDMP", "PSK", "SD", "SSL", "TLS", "VSS" ]: 88 token=u 89 result.append(token) 90 return " ".join( result ) 91 92 def getDatatype(self, name): 93 return self.json["datatype"][name] 94 95 def getResources(self, daemon): 96 return sorted(filter( None, self.json["resource"][daemon].keys()) ) 97 98 def getResource(self, daemon, resourcename): 99 return self.json["resource"][daemon][resourcename] 100 101 def getDefaultValue(self, data): 102 default="" 103 if data.get('default_value'): 104 default=data.get('default_value') 105 if data.get('platform_specific'): 106 default+=" (platform specific)" 107 return default 108 109 def getConvertedResources(self, daemon): 110 result = "" 111 for i in self.getResources(daemon): 112 result += i + "\n" 113 return result 114 115 def getResourceDirectives(self, daemon, resourcename): 116 return sorted(filter( None, self.getResource(daemon, resourcename).keys()) ) 117 118 def getResourceDirective(self, daemon, resourcename, directive, deprecated=None): 119 # TODO: 120 # deprecated: 121 # None: include deprecated 122 # False: exclude deprecated 123 # True: only deprecated 124 return BareosConfigurationSchemaDirective( self.json["resource"][daemon][resourcename][directive] ) 125 126 def getConvertedResourceDirectives(self, daemon, resourcename): 127 # OVERWRITE 128 return None 129 130 def writeResourceDirectives(self, daemon, resourcename, filename=None): 131 self.open(filename, "w") 132 self.out.write(self.getConvertedResourceDirectives(daemon, resourcename)) 133 self.close() 134 135 def getStringsWithModifiers(self, text, strings): 136 strings['text']=strings[text] 137 if strings[text]: 138 if strings.get('mo'): 139 return "%(mo)s%(text)s%(mc)s" % ( strings ) 140 else: 141 return "%(text)s" % ( strings ) 142 else: 143 return "" 144 145 146class BareosConfigurationSchemaDirective(dict): 147 148 def getDefaultValue( self ): 149 default=None 150 if dict.get( self, 'default_value' ): 151 if (dict.get( self, 'default_value' ) == "true") or (dict.get( self, 'default_value' ) == "on"): 152 default="yes" 153 elif (dict.get( self, 'default_value' ) == "false") or (dict.get( self, 'default_value' ) == "off"): 154 default="no" 155 else: 156 default=dict.get( self, 'default_value' ) 157 return default 158 159 def getStartVersion( self ): 160 if dict.get( self, 'versions' ): 161 version = dict.get( self, 'versions' ).partition("-")[0] 162 if version: 163 return version 164 165 def getEndVersion( self ): 166 if dict.get( self, 'versions' ): 167 version = dict.get( self, 'versions' ).partition("-")[2] 168 if version: 169 return version 170 171 def get(self, key, default=None): 172 if key == "default_value" or key == "default": 173 return self.getDefaultValue() 174 elif key == "start_version": 175 if self.getStartVersion(): 176 return self.getStartVersion() 177 elif key == "end_version": 178 if self.getEndVersion(): 179 return self.getEndVersion() 180 return dict.get(self, key, default) 181 182 183class BareosConfigurationSchema2Latex(BareosConfigurationSchema): 184 185 def getConvertedResources(self, daemon): 186 result = "\\begin{itemize}\n" 187 for i in self.getResources(daemon): 188 if i: 189 result += " \\item " + i + "\n" 190 result += "\\end{itemize}\n" 191 return result 192 193 def getLatexDatatypeRef( self, datatype ): 194 DataType="".join([x.capitalize() for x in datatype.split('_')]) 195 return "\\dt{%(DataType)s}" % { 'DataType': DataType } 196 197 def getLatexDefaultValue( self, data ): 198 default="" 199 if data.get( 'default_value' ): 200 default=data.get( 'default_value' ) 201 if data.get( 'platform_specific' ): 202 default+=" \\textit{\\small(platform specific)}" 203 return default 204 205 def getLatexDescription(self, data): 206 description = "" 207 if data.get('description'): 208 description = data.get('description').replace('_','\_') 209 return description 210 211 def getLatexTable(self, subtree, latexDefine="define%(key)s", latexLink="\\hyperlink{key%(key)s}{%(key)s}" ): 212 result="\\begin{center}\n" 213 result+="\\begin{longtable}{ l | l | l | l }\n" 214 result+="\\hline \n" 215 result+="\\multicolumn{1}{ c|}{\\textbf{%(name)-80s}} &\n" % { 'name': "configuration directive name" } 216 result+="\\multicolumn{1}{|c|}{\\textbf{%(type)-80s}} &\n" % { 'type': "type of data" } 217 result+="\\multicolumn{1}{|c|}{\\textbf{%(default)-80s}} &\n" % { 'default': "default value" } 218 result+="\\multicolumn{1}{|c }{\\textbf{%(remark)-80s}} \\\\ \n" % { 'remark': "remark" } 219 result+="\\hline \n" 220 result+="\\hline \n" 221 222 for key in sorted(filter( None, subtree.keys() ) ): 223 data=BareosConfigurationSchemaDirective(subtree[key]) 224 225 strings={ 226 'key': self.convertCamelCase2Spaces( key ), 227 'mc': "}", 228 'extra': [], 229 'default': self.getLatexDefaultValue( data ), 230 } 231 232 strings['directive_link'] = latexLink % strings 233 234 strings["datatype"] = self.getLatexDatatypeRef( data['datatype'] ) 235 if data.get( 'equals' ): 236 strings["datatype"]="= %(datatype)s" % { 'datatype': strings["datatype"] } 237 else: 238 strings["datatype"]="\{ %(datatype)s \}" % { 'datatype': strings["datatype"] } 239 240 extra=[] 241 if data.get( 'alias' ): 242 extra.append("alias") 243 strings["mo"]="\\textit{" 244 if data.get( 'deprecated' ): 245 extra.append("deprecated") 246 strings["mo"]="\\textit{" 247 if data.get( 'required' ): 248 extra.append("required") 249 strings["mo"]="\\textbf{" 250 strings["extra"]=", ".join(extra) 251 252 define = "\\csgdef{resourceDirectiveDefined" + latexDefine + "}{yes}" 253 strings['define']= define % strings 254 strings["t_directive"] = self.getStringsWithModifiers( "directive_link", strings ) 255 strings["t_datatype"] = self.getStringsWithModifiers( "datatype", strings ) 256 strings["t_default"] = self.getStringsWithModifiers( "default", strings ) 257 strings["t_extra"] = self.getStringsWithModifiers( "extra", strings ) 258 259 result+="%(define)-80s\n" % ( strings ) 260 result+="%(t_directive)-80s &\n" % ( strings ) 261 result+="%(t_datatype)-80s &\n" % ( strings ) 262 result+="%(t_default)-80s &\n" % ( strings ) 263 result+="%(t_extra)s\n" % ( strings ) 264 result+="\\\\ \n\n" % ( strings ) 265 266 result+="\\hline \n" 267 result+="\\end{longtable}\n" 268 result+="\\end{center}\n" 269 result+="\n" 270 return result 271 272 def writeResourceDirectivesTable(self, daemon, resourcename, filename=None): 273 ds=daemonName.getShort(daemon) 274 self.open(filename, "w") 275 self.out.write( self.getLatexTable( self.json["resource"][daemon][resourcename], latexDefine=ds+resourcename+"%(key)s", latexLink="\\linkResourceDirective*{"+ds+"}{"+resourcename+"}{%(key)s}" ) ) 276 self.close() 277 278 def writeDatatypeOptionsTable(self, filename=None): 279 self.open(filename, "w") 280 self.out.write(self.getLatexTable(self.getDatatype( "OPTIONS" )["values"], latexDefine="DatatypeOptions%(key)s" ) 281 ) 282 self.close() 283 284 def getConvertedResourceDirectives(self, daemon, resourcename): 285 result="\\begin{description}\n\n" 286 for directive in self.getResourceDirectives(daemon, resourcename): 287 data=self.getResourceDirective(daemon, resourcename, directive) 288 289 strings={ 290 'daemon': daemonName.getShort( daemon ), 291 'resource': resourcename, 292 'directive': self.convertCamelCase2Spaces( directive ), 293 'datatype': self.getLatexDatatypeRef( data['datatype'] ), 294 'default': self.getLatexDefaultValue( data ), 295 'version': data.get( 'start_version', "" ), 296 'description': self.getLatexDescription(data), 297 'required': '', 298 } 299 300 if data.get( 'alias' ): 301 if not strings['description']: 302 strings['description']="\\textit{This directive is an alias.}" 303 if data.get( 'deprecated' ): 304 # overwrites start_version 305 strings['version']="deprecated" 306 if data.get( 'required' ): 307 strings['required']="required" 308 309 result+="\\resourceDirective{%(daemon)s}{%(resource)s}{%(directive)s}{%(datatype)s}{%(required)s}{%(default)s}{%(version)s}{%(description)s}\n\n" % ( strings ) 310 result+="\\end{description}\n\n" 311 return result 312 313 def writeResourceDirectives(self, daemon, resourcename, filename=None): 314 self.open(filename, "w") 315 self.out.write( self.getConvertedResourceDirectives( daemon, resourcename ) ) 316 self.close() 317 318 def getResourceDirectiveDefs(self, daemon, resourcename): 319 result="" 320 for directive in self.getResourceDirectives(daemon, resourcename): 321 data=self.getResourceDirective(daemon, resourcename, directive) 322 323 strings={ 324 'daemon': daemonName.getShort( daemon ), 325 'resource': resourcename, 326 'directive': self.convertCamelCase2Spaces( directive ), 327 } 328 329 result+="\\defDirective{%(daemon)s}{%(resource)s}{%(directive)s}{}{}{%%\n" % ( strings ) 330 result+="}\n\n" 331 return result 332 333 def writeResourceDirectiveDefs(self, daemon, resourcename, filename=None): 334 self.open(filename, "w") 335 self.out.write( self.getResourceDirectiveDefs( daemon, resourcename ) ) 336 self.close() 337 338 339class BareosConfigurationSchema2Sphinx(BareosConfigurationSchema): 340 341 def indent(self, text, amount, ch=' '): 342 padding = amount * ch 343 return ''.join(padding+line for line in text.splitlines(True)) 344 345 def getLatexDatatypeRef( self, datatype ): 346 DataType="".join([x.capitalize() for x in datatype.split('_')]) 347 return "\\dt{%(DataType)s}" % { 'DataType': DataType } 348 349 def getDefaultValue( self, data ): 350 default="" 351 if data.get( 'default_value' ): 352 default=data.get( 'default_value' ) 353 if data.get( 'platform_specific' ): 354 default+=" *(platform specific)*" 355 return default 356 357 def getDescription(self, data): 358 description = "" 359 if data.get('description'): 360 description = self.indent(data.get('description'), 3) 361 #.replace('_','\_') 362 return description 363 364 def getConvertedResourceDirectives(self, daemon, resourcename): 365 logger = logging.getLogger() 366 367 result = '' 368 # only useful, when file is included by toctree. 369 #result='{}\n{}\n\n'.format(resourcename, len(resourcename) * '-') 370 for directive in self.getResourceDirectives(daemon,resourcename): 371 data=self.getResourceDirective(daemon, resourcename, directive) 372 373 strings={ 374 'program': daemon, 375 'daemon': daemonName.getLowShort(daemon), 376 'resource': resourcename.lower(), 377 'directive': directive , 378 'datatype': data['datatype'], 379 'default': self.getDefaultValue( data ), 380 'version': data.get( 'start_version', "" ), 381 'description': self.getDescription(data), 382 'required': '', 383 } 384 385 if data.get( 'alias' ): 386 if not strings['description']: 387 strings['description']=" *This directive is an alias.*" 388 389 if data.get( 'deprecated' ): 390 # overwrites start_version 391 strings['version']="deprecated" 392 393 includefilename = '/manually_added_config_directive_descriptions/{daemon}-{resource}-{directive}.rst.inc'.format(**strings) 394 395 396 result += '.. config:option:: {daemon}/{resource}/{directive}\n\n'.format(**strings) 397 398 if data.get( 'required' ): 399 strings['required']="True" 400 result += ' :required: {required}\n'.format(**strings) 401 402 result += ' :type: {datatype}\n'.format(**strings) 403 404 if data.get( 'default_value' ): 405 result += ' :default: {default}\n'.format(**strings) 406 407 if strings.get('version'): 408 result += ' :version: {version}\n'.format(**strings) 409 410 result += '\n' 411 412 if strings['description']: 413 result += strings['description'] + '\n\n' 414 415 # make sure, file exists, so that there are no problems with include. 416 checkincludefilename = 'source/{}'.format(includefilename) 417 if not os.path.exists(checkincludefilename): 418 touch(checkincludefilename) 419 420 result += ' .. include:: {}\n\n'.format(includefilename) 421 422 result += '\n\n' 423 424 return result 425 426 427 def getHeader(self): 428 result = '.. csv-table::\n' 429 result += ' :header: ' 430 result += self.getHeaderColumns() 431 result += '\n\n' 432 return result 433 434 def getHeaderColumns(self): 435 columns = [ 436 "configuration directive name", 437 "type of data", 438 "default value", 439 "remark" 440 ] 441 442 return '"{}"'.format('", "'.join(columns)) 443 444 445 def getRows(self, daemon, resourcename, subtree, link): 446 result ='' 447 for key in sorted(filter( None, subtree.keys() ) ): 448 data=BareosConfigurationSchemaDirective(subtree[key]) 449 450 strings={ 451 'key': self.convertCamelCase2Spaces( key ), 452 'extra': [], 453 'mc': '* ', 454 'default': self.getDefaultValue( data ), 455 456 'program': daemon, 457 'daemon': daemonName.getLowShort(daemon), 458 'resource': resourcename.lower(), 459 'directive': key, 460 } 461 462 strings['directive_link'] = link % strings 463 464 if data.get( 'equals' ): 465 strings["datatype"]="= %(datatype)s" % { 'datatype': data["datatype"] } 466 else: 467 strings["datatype"]="\{ %(datatype)s \}" % { 'datatype': data["datatype"] } 468 469 extra=[] 470 if data.get( 'alias' ): 471 extra.append("alias") 472 strings["mo"]="*" 473 if data.get( 'deprecated' ): 474 extra.append("deprecated") 475 strings["mo"]="*" 476 if data.get( 'required' ): 477 extra.append("required") 478 strings["mo"]="**" 479 strings["mc"]="** " 480 strings["extra"]=', '.join(extra) 481 482 strings["t_datatype"] = '"{}"'.format(self.getStringsWithModifiers( "datatype", strings )) 483 strings["t_default"] = '"{}"'.format(self.getStringsWithModifiers( "default", strings )) 484 strings["t_extra"] = '"{}"'.format(self.getStringsWithModifiers( "extra", strings )) 485 486 #result+=' %(directive_link)-60s, %(t_datatype)-20s, %(t_default)-20s, %(t_extra)s\n' % ( strings ) 487 result+=' %(directive_link)-60s, %(t_datatype)s, %(t_default)s, %(t_extra)s\n' % ( strings ) 488 489 return result 490 491 492 def getFooter(self): 493 result = "\n" 494 return result 495 496 497 def getTable(self, daemon, resourcename, subtree, link=':config:option:`%(daemon)s/%(resource)s/%(directive)s`\ ' ): 498 result = self.getHeader() 499 result += self.getRows(daemon, resourcename, subtree, link) 500 result += self.getFooter() 501 502 return result 503 504 def writeResourceDirectivesTable(self, daemon, resourcename, filename=None): 505 ds=daemonName.getShort(daemon) 506 self.open(filename, "w") 507 self.out.write( self.getTable(daemon, resourcename, self.json["resource"][daemon][resourcename] ) ) 508 self.close() 509 510 511 512def createLatex(data): 513 logger = logging.getLogger() 514 515 logger.info("Create LaTex files ...") 516 517 latex = BareosConfigurationSchema2Latex(data) 518 519 for daemon in latex.getDaemons(): 520 521 #pprint(schema.getResources(daemon)) 522 for resource in latex.getResources(daemon): 523 logger.info( "daemon: " + daemon + ", resource: " + resource ) 524 525 #pprint(schema.getResource(daemon,resource)) 526 latex.writeResourceDirectives(daemon, resource, "source/include/autogenerated/" + daemon.lower()+ "-resource-"+resource.lower()+"-description.tex") 527 latex.writeResourceDirectiveDefs(daemon, resource, "source/include/autogenerated/" + daemon.lower()+ "-resource-"+resource.lower()+"-defDirective.tex") 528 latex.writeResourceDirectivesTable(daemon, resource, "source/include/autogenerated/" + daemon.lower()+ "-resource-"+resource.lower()+"-table.tex") 529 530 if latex.getDatatypes(): 531 print latex.getDatatypes() 532 #print schema.getDatatype( "OPTIONS" ) 533 #print latex.getLatexTable( schema.getDatatype( "OPTIONS" )["values"], latexDefine="%(key)s", latexLink="\\linkResourceDirective{%(key)s}" ) 534 latex.writeDatatypeOptionsTable( filename="source/include/autogenerated/datatype-options-table.tex" ) 535 536 537 538def createSphinx(data): 539 logger = logging.getLogger() 540 541 logger.info("Create RST/Sphinx files ...") 542 543 sphinx = BareosConfigurationSchema2Sphinx(data) 544 545 for daemon in sphinx.getDaemons(): 546 547 #pprint(schema.getResources(daemon)) 548 for resource in sphinx.getResources(daemon): 549 logger.info( "daemon: " + daemon + ", resource: " + resource ) 550 551 sphinx.writeResourceDirectives(daemon, resource, "source/include/autogenerated/" + daemon.lower()+ "-resource-"+resource.lower()+"-description.rst.inc") 552 553 sphinx.writeResourceDirectivesTable(daemon, resource, "source/include/autogenerated/" + daemon.lower()+ "-resource-"+resource.lower()+"-table.rst.inc") 554 555 556 #if sphinx.getDatatypes(): 557 # print sphinx.getDatatypes() 558 559 560 561if __name__ == '__main__': 562 logging.basicConfig(format='%(levelname)s %(module)s.%(funcName)s: %(message)s', level=logging.INFO) 563 logger = logging.getLogger() 564 565 parser = argparse.ArgumentParser() 566 parser.add_argument('-q', '--quiet', action='store_true', help="suppress logging output" ) 567 parser.add_argument('-d', '--debug', action='store_true', help="enable debugging output" ) 568 parser.add_argument('--latex', action='store_true', help="Create LaTex files." ) 569 parser.add_argument('--sphinx', action='store_true', help="Create RST files for Sphinx." ) 570 parser.add_argument("filename", help="json data file") 571 args = parser.parse_args() 572 if args.debug: 573 logger.setLevel(logging.DEBUG) 574 575 if args.quiet: 576 logger.setLevel(logging.CRITICAL) 577 578 with open(args.filename) as data_file: 579 data = json.load(data_file) 580 #pprint(data) 581 582 if not args.latex: 583 # default is sphinx 584 args.sphinx = True 585 586 if args.latex: 587 createLatex(data) 588 589 if args.sphinx: 590 createSphinx(data) 591