summaryrefslogtreecommitdiff
path: root/conf/format-options.py
diff options
context:
space:
mode:
authorYves-Alexis Perez <corsac@debian.org>2014-03-11 20:48:48 +0100
committerYves-Alexis Perez <corsac@debian.org>2014-03-11 20:48:48 +0100
commit15fb7904f4431a6e7c305fd08732458f7f885e7e (patch)
treec93b60ee813af70509f00f34e29ebec311762427 /conf/format-options.py
parent5313d2d78ca150515f7f5eb39801c100690b6b29 (diff)
downloadvyos-strongswan-15fb7904f4431a6e7c305fd08732458f7f885e7e.tar.gz
vyos-strongswan-15fb7904f4431a6e7c305fd08732458f7f885e7e.zip
Imported Upstream version 5.1.2
Diffstat (limited to 'conf/format-options.py')
-rwxr-xr-xconf/format-options.py337
1 files changed, 337 insertions, 0 deletions
diff --git a/conf/format-options.py b/conf/format-options.py
new file mode 100755
index 000000000..04afed6d6
--- /dev/null
+++ b/conf/format-options.py
@@ -0,0 +1,337 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2014 Tobias Brunner
+# Hochschule fuer Technik Rapperswil
+#
+# 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
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+
+"""
+Parses strongswan.conf option descriptions and produces configuration file
+and man page snippets.
+
+The format for description files is as follows:
+
+full.option.name [[:]= default]
+ Short description intended as comment in config snippet
+
+ Long description for use in the man page, with
+ simple formatting: _italic_, **bold**
+
+ Second paragraph of the long description
+
+The descriptions must be indented by tabs or spaces but are both optional.
+If only a short description is given it is used for both intended usages.
+Line breaks within a paragraph of the long description or the short description
+are not preserved. But multiple paragraphs will be separated in the man page.
+Any formatting in the short description is removed when producing config
+snippets.
+
+Options for which a value is assigned with := are not commented out in the
+produced configuration file snippet. This allows to override a default value,
+that e.g. has to be preserved for legacy reasons, in the generated default
+config.
+
+To describe sections the following format can be used:
+
+full.section.name {[#]}
+ Short description of this section
+
+ Long description as above
+
+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.
+"""
+
+import sys
+import re
+from textwrap import TextWrapper
+from optparse import OptionParser
+
+class ConfigOption:
+ """Representing a configuration option or described section in strongswan.conf"""
+ def __init__(self, name, default = None, section = False, commented = False):
+ self.name = name.split('.')[-1]
+ self.fullname = name
+ self.default = default
+ self.section = section
+ self.commented = commented
+ self.desc = []
+ self.options = []
+
+ def __cmp__(self, other):
+ if self.section == other.section:
+ return cmp(self.name, other.name)
+ return 1 if self.section else -1
+
+ def add_paragraph(self):
+ """Adds a new paragraph to the description"""
+ if len(self.desc) and len(self.desc[-1]):
+ self.desc.append("")
+
+ def add(self, line):
+ """Adds a line to the last paragraph"""
+ if not len(self.desc):
+ self.desc.append(line)
+ elif not len(self.desc[-1]):
+ self.desc[-1] = line
+ else:
+ self.desc[-1] += ' ' + line
+
+ def adopt(self, other):
+ """Adopts settings from other, which should be more recently parsed"""
+ self.default = other.default
+ self.commented = other.commented
+ self.desc = other.desc
+
+class Parser:
+ """Parses one or more files of configuration options"""
+ def __init__(self):
+ self.options = []
+
+ def parse(self, file):
+ """Parses the given file and adds all options to the internal store"""
+ self.__current = None
+ for line in file:
+ self.__parse_line(line)
+ if self.__current:
+ self.__add_option(self.__current)
+
+ def __parse_line(self, line):
+ """Parses a single line"""
+ if re.match(r'^\s*#', line):
+ return
+ # option definition
+ m = re.match(r'^(?P<name>\S+)\s*((?P<assign>:)?=\s*(?P<default>.+)?)?\s*$', line)
+ if m:
+ if self.__current:
+ self.__add_option(self.__current)
+ self.__current = ConfigOption(m.group('name'), m.group('default'),
+ commented = not m.group('assign'))
+ return
+ # section definition
+ m = re.match(r'^(?P<name>\S+)\s*\{\s*(?P<comment>#)?\s*\}\s*$', line)
+ if m:
+ if self.__current:
+ self.__add_option(self.__current)
+ self.__current = ConfigOption(m.group('name'), section = True,
+ commented = m.group('comment'))
+ return
+ # paragraph separator
+ m = re.match(r'^\s*$', line)
+ if m and self.__current:
+ self.__current.add_paragraph()
+ # description line
+ m = re.match(r'^\s+(?P<text>.+?)\s*$', line)
+ if m and self.__current:
+ self.__current.add(m.group('text'))
+
+ def __add_option(self, option):
+ """Adds the given option to the abstract storage"""
+ option.desc = [desc for desc in option.desc if len(desc)]
+ parts = option.fullname.split('.')
+ parent = self.__get_option(parts[:-1], True)
+ if not parent:
+ parent = self
+ found = next((x for x in parent.options if x.name == option.name
+ and x.section == option.section), None)
+ if found:
+ found.adopt(option)
+ else:
+ parent.options.append(option)
+ parent.options.sort()
+
+ def __get_option(self, parts, create = False):
+ """Searches/Creates the option (section) based on a list of section names"""
+ option = None
+ options = self.options
+ fullname = ""
+ for name in parts:
+ fullname += '.' + name if len(fullname) else name
+ option = next((x for x in options if x.name == name and x.section), None)
+ if not option:
+ if not create:
+ break
+ option = ConfigOption(fullname, section = True)
+ options.append(option)
+ options.sort()
+ options = option.options
+ return option
+
+ def get_option(self, name):
+ """Retrieves the option with the given name"""
+ return self.__get_option(name.split('.'))
+
+class TagReplacer:
+ """Replaces formatting tags in text"""
+ def __init__(self):
+ self.__matcher_b = self.__create_matcher('**')
+ self.__matcher_i = self.__create_matcher('_')
+ self.__replacer = None
+
+ def __create_matcher(self, tag):
+ tag = re.escape(tag)
+ return re.compile(r'''
+ (^|\s|(?P<brack>[(\[])) # prefix with optional opening bracket
+ (?P<tag>''' + tag + r''') # start tag
+ (?P<text>\w|\S.*?\S) # text
+ ''' + tag + r''' # end tag
+ (?P<punct>([.,!:)\]]|\(\d+\))*) # punctuation
+ (?=$|\s) # suffix (don't consume it so that subsequent tags can match)
+ ''', flags = re.DOTALL | re.VERBOSE)
+
+ def _create_replacer(self):
+ def replacer(m):
+ punct = m.group('punct')
+ if not punct:
+ punct = ''
+ return '{0}{1}{2}'.format(m.group(1), m.group('text'), punct)
+ return replacer
+
+ def replace(self, text):
+ if not self.__replacer:
+ self.__replacer = self._create_replacer()
+ text = re.sub(self.__matcher_b, self.__replacer, text)
+ return re.sub(self.__matcher_i, self.__replacer, text)
+
+class GroffTagReplacer(TagReplacer):
+ def _create_replacer(self):
+ def replacer(m):
+ nl = '\n' if m.group(1) else ''
+ format = 'I' if m.group('tag') == '_' else 'B'
+ brack = m.group('brack')
+ if not brack:
+ brack = ''
+ punct = m.group('punct')
+ if not punct:
+ punct = ''
+ text = re.sub(r'[\r\n\t]', ' ', m.group('text'))
+ return '{0}.R{1} "{2}" "{3}" "{4}"\n'.format(nl, format, brack, text, punct)
+ return replacer
+
+class ConfFormatter:
+ """Formats options to a strongswan.conf snippet"""
+ def __init__(self):
+ self.__indent = ' '
+ self.__wrapper = TextWrapper(width = 80, replace_whitespace = True,
+ break_long_words = False, break_on_hyphens = False)
+ self.__tags = TagReplacer()
+
+ def __print_description(self, opt, indent):
+ if len(opt.desc):
+ self.__wrapper.initial_indent = '{0}# '.format(self.__indent * indent)
+ self.__wrapper.subsequent_indent = self.__wrapper.initial_indent
+ print format(self.__wrapper.fill(self.__tags.replace(opt.desc[0])))
+
+ def __print_option(self, opt, indent, commented):
+ """Print a single option with description and default value"""
+ comment = "# " if commented or opt.commented else ""
+ self.__print_description(opt, indent)
+ if 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)
+ print
+
+ def __print_section(self, section, indent, commented):
+ """Print a section with all options"""
+ comment = "# " if commented or section.commented else ""
+ self.__print_description(section, indent)
+ print '{0}{1}{2} {{'.format(self.__indent * indent, comment, section.name)
+ print
+ for o in section.options:
+ if o.section:
+ self.__print_section(o, indent + 1, section.commented)
+ else:
+ self.__print_option(o, indent + 1, section.commented)
+ print '{0}{1}}}'.format(self.__indent * indent, comment)
+ print
+
+ def format(self, options):
+ """Print a list of options"""
+ if not options:
+ return
+ for option in options:
+ if option.section:
+ self.__print_section(option, 0, False)
+ else:
+ self.__print_option(option, 0, False)
+
+class ManFormatter:
+ """Formats a list of options into a groff snippet"""
+ def __init__(self):
+ self.__wrapper = TextWrapper(width = 80, replace_whitespace = False,
+ break_long_words = False, break_on_hyphens = False)
+ self.__tags = GroffTagReplacer()
+
+ def __groffize(self, text):
+ """Encode text as groff text"""
+ text = self.__tags.replace(text)
+ text = re.sub(r'(?<!\\)-', r'\\-', text)
+ # remove any leading whitespace
+ return re.sub(r'^\s+', '', text, flags = re.MULTILINE)
+
+ def __format_option(self, option):
+ """Print a single option"""
+ if option.section and not len(option.desc):
+ return
+ if option.section:
+ print '.TP\n.B {0}\n.br'.format(option.fullname)
+ else:
+ print '.TP'
+ default = option.default if option.default else ''
+ print '.BR {0} " [{1}]"'.format(option.fullname, default)
+ for para in option.desc if len(option.desc) < 2 else option.desc[1:]:
+ print self.__groffize(self.__wrapper.fill(para))
+ print ''
+
+ def format(self, options):
+ """Print a list of options"""
+ if not options:
+ return
+ for option in options:
+ if option.section:
+ self.__format_option(option)
+ self.format(option.options)
+ 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")
+(opts, args) = options.parse_args()
+
+parser = Parser()
+if len(args):
+ for filename in args:
+ try:
+ with open(filename, 'r') as file:
+ parser.parse(file)
+ except IOError as e:
+ sys.stderr.write("Unable to open '{0}': {1}\n".format(filename, e.strerror))
+else:
+ parser.parse(sys.stdin)
+
+options = parser.options
+if (opts.root):
+ root = parser.get_option(opts.root)
+ if root:
+ options = root.options
+
+if opts.format == "conf":
+ formatter = ConfFormatter()
+elif opts.format == "man":
+ formatter = ManFormatter()
+
+formatter.format(options)