]>
git.ipfire.org Git - thirdparty/strongswan.git/blob - conf/format-options.py
3 # Copyright (C) 2014-2015 Tobias Brunner
4 # Hochschule fuer Technik Rapperswil
6 # This program is free software; you can redistribute it and/or modify it
7 # under the terms of the GNU General Public License as published by the
8 # Free Software Foundation; either version 2 of the License, or (at your
9 # option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
17 Parses strongswan.conf option descriptions and produces configuration file
18 and man page snippets.
20 The format for description files is as follows:
22 full.option.name [[:]= default]
23 Short description intended as comment in config snippet
25 Long description for use in the man page, with
26 simple formatting: _italic_, **bold**
28 Second paragraph of the long description
30 The descriptions must be indented by tabs or spaces but are both optional.
31 If only a short description is given it is used for both intended usages.
32 Line breaks within a paragraph of the long description or the short description
33 are not preserved. But multiple paragraphs will be separated in the man page.
34 Any formatting in the short description is removed when producing config
37 Options for which a value is assigned with := are not commented out in the
38 produced configuration file snippet. This allows to override a default value,
39 that e.g. has to be preserved for legacy reasons, in the generated default
42 To describe sections the following format can be used:
44 full.section.name {[#]}
45 Short description of this section
47 Long description as above
49 If a # is added between the curly braces the section header will be commented
50 out in the configuration file snippet, which is useful for example sections.
52 Dots in section/option names may be escaped with a backslash. For instance,
53 with the following section description
55 charon.filelog./var/log/daemon\.log {}
56 Section to define logging into /var/log/daemon.log
58 /var/log/daemon.log will be the name of the last section.
63 from textwrap
import TextWrapper
64 from optparse
import OptionParser
65 from operator
import attrgetter
68 """Representing a configuration option or described section in strongswan.conf"""
69 def __init__(self
, path
, default
= None, section
= False, commented
= False):
72 self
.fullname
= '.'.join(path
)
73 self
.default
= default
74 self
.section
= section
75 self
.commented
= commented
79 def __lt__(self
, other
):
80 return self
.name
< other
.name
82 def add_paragraph(self
):
83 """Adds a new paragraph to the description"""
84 if len(self
.desc
) and len(self
.desc
[-1]):
88 """Adds a line to the last paragraph"""
89 if not len(self
.desc
):
90 self
.desc
.append(line
)
91 elif not len(self
.desc
[-1]):
94 self
.desc
[-1] += ' ' + line
96 def adopt(self
, other
):
97 """Adopts settings from other, which should be more recently parsed"""
98 self
.default
= other
.default
99 self
.commented
= other
.commented
100 self
.desc
= other
.desc
103 """Parses one or more files of configuration options"""
104 def __init__(self
, sort
= True):
108 def parse(self
, file):
109 """Parses the given file and adds all options to the internal store"""
110 self
.__current
= None
112 self
.__parse
_line
(line
)
114 self
.__add
_option
(self
.__current
)
116 def __parse_line(self
, line
):
117 """Parses a single line"""
118 if re
.match(r
'^\s*#', line
):
121 m
= re
.match(r
'^(?P<name>\S+)\s*((?P<assign>:)?=\s*(?P<default>.+)?)?\s*$', line
)
124 self
.__add
_option
(self
.__current
)
125 path
= self
.__split
_name
(m
.group('name'))
126 self
.__current
= ConfigOption(path
, m
.group('default'),
127 commented
= not m
.group('assign'))
130 m
= re
.match(r
'^(?P<name>\S+)\s*\{\s*(?P<comment>#)?\s*\}\s*$', line
)
133 self
.__add
_option
(self
.__current
)
134 path
= self
.__split
_name
(m
.group('name'))
135 self
.__current
= ConfigOption(path
, section
= True,
136 commented
= m
.group('comment'))
138 # paragraph separator
139 m
= re
.match(r
'^\s*$', line
)
140 if m
and self
.__current
:
141 self
.__current
.add_paragraph()
143 m
= re
.match(r
'^\s+(?P<text>.+?)\s*$', line
)
144 if m
and self
.__current
:
145 self
.__current
.add(m
.group('text'))
147 def __split_name(self
, name
):
148 """Split the given full name in a list of section/option names"""
149 return [x
.replace('\.', '.') for x
in re
.split(r
'(?<!\\)\.', name
)]
151 def __add_option(self
, option
):
152 """Adds the given option to the abstract storage"""
153 option
.desc
= [desc
for desc
in option
.desc
if len(desc
)]
154 parent
= self
.__get
_option
(option
.path
[:-1], True)
157 found
= next((x
for x
in parent
.options
if x
.name
== option
.name
158 and x
.section
== option
.section
), None)
162 parent
.options
.append(option
)
164 parent
.options
.sort()
166 def __get_option(self
, path
, create
= False):
167 """Searches/Creates the option (section) based on a list of section names"""
169 options
= self
.options
170 for i
, name
in enumerate(path
, 1):
171 option
= next((x
for x
in options
if x
.name
== name
and x
.section
), None)
175 option
= ConfigOption(path
[:i
], section
= True)
176 options
.append(option
)
179 options
= option
.options
182 def get_option(self
, name
):
183 """Retrieves the option with the given name"""
184 return self
.__get
_option
(self
.__split
_name
(name
))
187 """Replaces formatting tags in text"""
189 self
.__matcher
_b
= self
.__create
_matcher
('**')
190 self
.__matcher
_i
= self
.__create
_matcher
('_')
191 self
.__replacer
= None
193 def __create_matcher(self
, tag
):
195 return re
.compile(r
'''
196 (^|\s|(?P<brack>[(\[])) # prefix with optional opening bracket
197 (?P<tag>''' + tag
+ r
''') # start tag
198 (?P<text>\w|\S.*?\S) # text
199 ''' + tag
+ r
''' # end tag
200 (?P<punct>([.,!:)\]]|\(\d+\))*) # punctuation
201 (?=$|\s) # suffix (don't consume it so that subsequent tags can match)
202 ''', flags
= re
.DOTALL | re
.VERBOSE
)
204 def _create_replacer(self
):
206 punct
= m
.group('punct')
209 return '{0}{1}{2}'.format(m
.group(1), m
.group('text'), punct
)
212 def replace(self
, text
):
213 if not self
.__replacer
:
214 self
.__replacer
= self
._create
_replacer
()
215 text
= re
.sub(self
.__matcher
_b
, self
.__replacer
, text
)
216 return re
.sub(self
.__matcher
_i
, self
.__replacer
, text
)
218 class GroffTagReplacer(TagReplacer
):
219 def _create_replacer(self
):
221 nl
= '\n' if m
.group(1) else ''
222 format
= 'I' if m
.group('tag') == '_' else 'B'
223 brack
= m
.group('brack')
226 punct
= m
.group('punct')
229 text
= re
.sub(r
'[\r\n\t]', ' ', m
.group('text'))
230 return '{0}.R{1} "{2}" "{3}" "{4}"\n'.format(nl
, format
, brack
, text
, punct
)
234 """Formats options to a strongswan.conf snippet"""
237 self
.__wrapper
= TextWrapper(width
= 80, replace_whitespace
= True,
238 break_long_words
= False, break_on_hyphens
= False)
239 self
.__tags
= TagReplacer()
241 def __print_description(self
, opt
, indent
):
243 self
.__wrapper
.initial_indent
= '{0}# '.format(self
.__indent
* indent
)
244 self
.__wrapper
.subsequent_indent
= self
.__wrapper
.initial_indent
245 print(self
.__wrapper
.fill(self
.__tags
.replace(opt
.desc
[0])))
247 def __print_option(self
, opt
, indent
, commented
):
248 """Print a single option with description and default value"""
249 comment
= "# " if commented
or opt
.commented
else ""
250 self
.__print
_description
(opt
, indent
)
252 print('{0}{1}{2} = {3}'.format(self
.__indent
* indent
, comment
, opt
.name
, opt
.default
))
254 print('{0}{1}{2} ='.format(self
.__indent
* indent
, comment
, opt
.name
))
257 def __print_section(self
, section
, indent
, commented
):
258 """Print a section with all options"""
259 commented
= commented
or section
.commented
260 comment
= "# " if commented
else ""
261 self
.__print
_description
(section
, indent
)
262 print('{0}{1}{2} {{'.format(self
.__indent
* indent
, comment
, section
.name
))
264 for o
in sorted(section
.options
, key
=attrgetter('section')):
266 self
.__print
_section
(o
, indent
+ 1, commented
)
268 self
.__print
_option
(o
, indent
+ 1, commented
)
269 print('{0}{1}}}'.format(self
.__indent
* indent
, comment
))
272 def format(self
, options
):
273 """Print a list of options"""
276 for option
in sorted(options
, key
=attrgetter('section')):
278 self
.__print
_section
(option
, 0, False)
280 self
.__print
_option
(option
, 0, False)
283 """Formats a list of options into a groff snippet"""
285 self
.__wrapper
= TextWrapper(width
= 80, replace_whitespace
= False,
286 break_long_words
= False, break_on_hyphens
= False)
287 self
.__tags
= GroffTagReplacer()
289 def __groffize(self
, text
):
290 """Encode text as groff text"""
291 text
= self
.__tags
.replace(text
)
292 text
= re
.sub(r
'(?<!\\)-', r
'\\-', text
)
293 # remove any leading whitespace
294 return re
.sub(r
'^\s+', '', text
, flags
= re
.MULTILINE
)
296 def __format_option(self
, option
):
297 """Print a single option"""
298 if option
.section
and not len(option
.desc
):
301 print('.TP\n.B {0}\n.br'.format(option
.fullname
))
304 default
= option
.default
if option
.default
else ''
305 print('.BR {0} " [{1}]"'.format(option
.fullname
, default
))
306 for para
in option
.desc
if len(option
.desc
) < 2 else option
.desc
[1:]:
307 print(self
.__groffize
(self
.__wrapper
.fill(para
)))
310 def format(self
, options
):
311 """Print a list of options"""
314 for option
in options
:
316 self
.__format
_option
(option
)
317 self
.format(option
.options
)
319 self
.__format
_option
(option
)
321 options
= OptionParser(usage
= "Usage: %prog [options] file1 file2\n\n"
322 "If no filenames are provided the input is read from stdin.")
323 options
.add_option("-f", "--format", dest
="format", type="choice", choices
=["conf", "man"],
324 help="output format: conf, man [default: %default]", default
="conf")
325 options
.add_option("-r", "--root", dest
="root", metavar
="NAME",
326 help="root section of which options are printed, "
327 "if not found everything is printed")
328 options
.add_option("-n", "--nosort", action
="store_false", dest
="sort",
329 default
=True, help="do not sort sections alphabetically")
331 (opts
, args
) = options
.parse_args()
333 parser
= Parser(opts
.sort
)
335 for filename
in args
:
337 with
open(filename
, 'r') as file:
340 sys
.stderr
.write("Unable to open '{0}': {1}\n".format(filename
, e
.strerror
))
342 parser
.parse(sys
.stdin
)
344 options
= parser
.options
346 root
= parser
.get_option(opts
.root
)
348 options
= root
.options
350 if opts
.format
== "conf":
351 formatter
= ConfFormatter()
352 elif opts
.format
== "man":
353 formatter
= ManFormatter()
355 formatter
.format(options
)