-#!/usr/bin/env python
+#!/usr/bin/env python3
#
-# Copyright (C) 2014-2015 Tobias Brunner
-# Hochschule fuer Technik Rapperswil
+# Copyright (C) 2014-2019 Tobias Brunner
+#
+# Copyright (C) secunet Security Networks AG
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
If a # is added between the curly braces the section header will be commented
out in the configuration file snippet, which is useful for example sections.
+To add include statements to generated config files (ignored when generating
+man pages) the following format can be used:
+
+full.section.name.include files/to/include
+ Description of this include statement
+
Dots in section/option names may be escaped with a backslash. For instance,
with the following section description
import sys
import re
from textwrap import TextWrapper
-from optparse import OptionParser
-from operator import attrgetter
+from argparse import ArgumentParser
+from functools import cmp_to_key, total_ordering
+@total_ordering
class ConfigOption:
"""Representing a configuration option or described section in strongswan.conf"""
- def __init__(self, path, default = None, section = False, commented = False):
+ def __init__(self, path, default = None, section = False, commented = False, include = False):
self.path = path
self.name = path[-1]
self.fullname = '.'.join(path)
self.default = default
self.section = section
self.commented = commented
+ self.include = include
self.desc = []
self.options = []
+ def __eq__(self, other):
+ return self.name == other.name
+
def __lt__(self, other):
return self.name < other.name
self.commented = other.commented
self.desc = other.desc
+ @staticmethod
+ def cmp(a, b):
+ # order options before sections and includes last
+ if a.include or b.include:
+ return a.include - b.include
+ return a.section - b.section
+
class Parser:
"""Parses one or more files of configuration options"""
def __init__(self, sort = True):
self.__current = ConfigOption(path, section = True,
commented = m.group('comment'))
return
+ # include definition
+ m = re.match(r'^(?P<name>\S+\.include|include)\s+(?P<pattern>\S+)\s*$', line)
+ if m:
+ if self.__current:
+ self.__add_option(self.__current)
+ path = self.__split_name(m.group('name'))
+ self.__current = ConfigOption(path, m.group('pattern'), include = True)
+ return
# paragraph separator
m = re.match(r'^\s*$', line)
if m and self.__current:
return re.compile(r'''
(^|\s|(?P<brack>[(\[])) # prefix with optional opening bracket
(?P<tag>''' + tag + r''') # start tag
- (?P<text>\w|\S.*?\S) # text
+ (?P<text>\S|\S.*?\S) # text
''' + tag + r''' # end tag
(?P<punct>([.,!:)\]]|\(\d+\))*) # punctuation
(?=$|\s) # suffix (don't consume it so that subsequent tags can match)
"""Print a single option with description and default value"""
comment = "# " if commented or opt.commented else ""
self.__print_description(opt, indent)
- if opt.default:
+ if opt.include:
+ print('{0}{1} {2}'.format(self.__indent * indent, opt.name, opt.default))
+ elif opt.default:
print('{0}{1}{2} = {3}'.format(self.__indent * indent, comment, opt.name, opt.default))
else:
print('{0}{1}{2} ='.format(self.__indent * indent, comment, opt.name))
self.__print_description(section, indent)
print('{0}{1}{2} {{'.format(self.__indent * indent, comment, section.name))
print('')
- for o in sorted(section.options, key=attrgetter('section')):
+ for o in sorted(section.options, key=cmp_to_key(ConfigOption.cmp)):
if o.section:
self.__print_section(o, indent + 1, commented)
else:
"""Print a list of options"""
if not options:
return
- for option in sorted(options, key=attrgetter('section')):
+ for option in sorted(options, key=cmp_to_key(ConfigOption.cmp)):
if option.section:
self.__print_section(option, 0, False)
else:
"""Print a single option"""
if option.section and not len(option.desc):
return
+ if option.include:
+ return
if option.section:
print('.TP\n.B {0}\n.br'.format(option.fullname))
else:
else:
self.__format_option(option)
-options = OptionParser(usage = "Usage: %prog [options] file1 file2\n\n"
- "If no filenames are provided the input is read from stdin.")
-options.add_option("-f", "--format", dest="format", type="choice", choices=["conf", "man"],
- help="output format: conf, man [default: %default]", default="conf")
-options.add_option("-r", "--root", dest="root", metavar="NAME",
- help="root section of which options are printed, "
- "if not found everything is printed")
-options.add_option("-n", "--nosort", action="store_false", dest="sort",
- default=True, help="do not sort sections alphabetically")
+args = ArgumentParser()
+args.add_argument('file', nargs='*',
+ help="files to process, omit to read input from stdin")
+args.add_argument("-f", "--format", dest="format", choices=["conf", "man"],
+ help="output format (default: %(default)s)", default="conf")
+args.add_argument("-r", "--root", dest="root", metavar="NAME",
+ help="root section of which options are printed; everything"
+ "is printed if not found")
+args.add_argument("-n", "--nosort", action="store_false", dest="sort",
+ default=True, help="do not sort sections alphabetically")
-(opts, args) = options.parse_args()
+opts = args.parse_args()
parser = Parser(opts.sort)
-if len(args):
- for filename in args:
+if len(opts.file):
+ for filename in opts.file:
try:
with open(filename, 'r') as file:
parser.parse(file)