]>
git.ipfire.org Git - people/ms/strongswan.git/blob - conf/format-options.py
3 # Copyright (C) 2014 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.
55 from textwrap
import TextWrapper
56 from optparse
import OptionParser
57 from operator
import attrgetter
60 """Representing a configuration option or described section in strongswan.conf"""
61 def __init__(self
, name
, default
= None, section
= False, commented
= False):
62 self
.name
= name
.split('.')[-1]
64 self
.default
= default
65 self
.section
= section
66 self
.commented
= commented
70 def __lt__(self
, other
):
71 return self
.name
< other
.name
73 def add_paragraph(self
):
74 """Adds a new paragraph to the description"""
75 if len(self
.desc
) and len(self
.desc
[-1]):
79 """Adds a line to the last paragraph"""
80 if not len(self
.desc
):
81 self
.desc
.append(line
)
82 elif not len(self
.desc
[-1]):
85 self
.desc
[-1] += ' ' + line
87 def adopt(self
, other
):
88 """Adopts settings from other, which should be more recently parsed"""
89 self
.default
= other
.default
90 self
.commented
= other
.commented
91 self
.desc
= other
.desc
94 """Parses one or more files of configuration options"""
95 def __init__(self
, sort
= True):
99 def parse(self
, file):
100 """Parses the given file and adds all options to the internal store"""
101 self
.__current
= None
103 self
.__parse
_line
(line
)
105 self
.__add
_option
(self
.__current
)
107 def __parse_line(self
, line
):
108 """Parses a single line"""
109 if re
.match(r
'^\s*#', line
):
112 m
= re
.match(r
'^(?P<name>\S+)\s*((?P<assign>:)?=\s*(?P<default>.+)?)?\s*$', line
)
115 self
.__add
_option
(self
.__current
)
116 self
.__current
= ConfigOption(m
.group('name'), m
.group('default'),
117 commented
= not m
.group('assign'))
120 m
= re
.match(r
'^(?P<name>\S+)\s*\{\s*(?P<comment>#)?\s*\}\s*$', line
)
123 self
.__add
_option
(self
.__current
)
124 self
.__current
= ConfigOption(m
.group('name'), section
= True,
125 commented
= m
.group('comment'))
127 # paragraph separator
128 m
= re
.match(r
'^\s*$', line
)
129 if m
and self
.__current
:
130 self
.__current
.add_paragraph()
132 m
= re
.match(r
'^\s+(?P<text>.+?)\s*$', line
)
133 if m
and self
.__current
:
134 self
.__current
.add(m
.group('text'))
136 def __add_option(self
, option
):
137 """Adds the given option to the abstract storage"""
138 option
.desc
= [desc
for desc
in option
.desc
if len(desc
)]
139 parts
= option
.fullname
.split('.')
140 parent
= self
.__get
_option
(parts
[:-1], True)
143 found
= next((x
for x
in parent
.options
if x
.name
== option
.name
144 and x
.section
== option
.section
), None)
148 parent
.options
.append(option
)
150 parent
.options
.sort()
152 def __get_option(self
, parts
, create
= False):
153 """Searches/Creates the option (section) based on a list of section names"""
155 options
= self
.options
158 fullname
+= '.' + name
if len(fullname
) else name
159 option
= next((x
for x
in options
if x
.name
== name
and x
.section
), None)
163 option
= ConfigOption(fullname
, section
= True)
164 options
.append(option
)
167 options
= option
.options
170 def get_option(self
, name
):
171 """Retrieves the option with the given name"""
172 return self
.__get
_option
(name
.split('.'))
175 """Replaces formatting tags in text"""
177 self
.__matcher
_b
= self
.__create
_matcher
('**')
178 self
.__matcher
_i
= self
.__create
_matcher
('_')
179 self
.__replacer
= None
181 def __create_matcher(self
, tag
):
183 return re
.compile(r
'''
184 (^|\s|(?P<brack>[(\[])) # prefix with optional opening bracket
185 (?P<tag>''' + tag
+ r
''') # start tag
186 (?P<text>\w|\S.*?\S) # text
187 ''' + tag
+ r
''' # end tag
188 (?P<punct>([.,!:)\]]|\(\d+\))*) # punctuation
189 (?=$|\s) # suffix (don't consume it so that subsequent tags can match)
190 ''', flags
= re
.DOTALL | re
.VERBOSE
)
192 def _create_replacer(self
):
194 punct
= m
.group('punct')
197 return '{0}{1}{2}'.format(m
.group(1), m
.group('text'), punct
)
200 def replace(self
, text
):
201 if not self
.__replacer
:
202 self
.__replacer
= self
._create
_replacer
()
203 text
= re
.sub(self
.__matcher
_b
, self
.__replacer
, text
)
204 return re
.sub(self
.__matcher
_i
, self
.__replacer
, text
)
206 class GroffTagReplacer(TagReplacer
):
207 def _create_replacer(self
):
209 nl
= '\n' if m
.group(1) else ''
210 format
= 'I' if m
.group('tag') == '_' else 'B'
211 brack
= m
.group('brack')
214 punct
= m
.group('punct')
217 text
= re
.sub(r
'[\r\n\t]', ' ', m
.group('text'))
218 return '{0}.R{1} "{2}" "{3}" "{4}"\n'.format(nl
, format
, brack
, text
, punct
)
222 """Formats options to a strongswan.conf snippet"""
225 self
.__wrapper
= TextWrapper(width
= 80, replace_whitespace
= True,
226 break_long_words
= False, break_on_hyphens
= False)
227 self
.__tags
= TagReplacer()
229 def __print_description(self
, opt
, indent
):
231 self
.__wrapper
.initial_indent
= '{0}# '.format(self
.__indent
* indent
)
232 self
.__wrapper
.subsequent_indent
= self
.__wrapper
.initial_indent
233 print(self
.__wrapper
.fill(self
.__tags
.replace(opt
.desc
[0])))
235 def __print_option(self
, opt
, indent
, commented
):
236 """Print a single option with description and default value"""
237 comment
= "# " if commented
or opt
.commented
else ""
238 self
.__print
_description
(opt
, indent
)
240 print('{0}{1}{2} = {3}'.format(self
.__indent
* indent
, comment
, opt
.name
, opt
.default
))
242 print('{0}{1}{2} ='.format(self
.__indent
* indent
, comment
, opt
.name
))
245 def __print_section(self
, section
, indent
, commented
):
246 """Print a section with all options"""
247 commented
= commented
or section
.commented
248 comment
= "# " if commented
else ""
249 self
.__print
_description
(section
, indent
)
250 print('{0}{1}{2} {{'.format(self
.__indent
* indent
, comment
, section
.name
))
252 for o
in sorted(section
.options
, key
=attrgetter('section')):
254 self
.__print
_section
(o
, indent
+ 1, commented
)
256 self
.__print
_option
(o
, indent
+ 1, commented
)
257 print('{0}{1}}}'.format(self
.__indent
* indent
, comment
))
260 def format(self
, options
):
261 """Print a list of options"""
264 for option
in sorted(options
, key
=attrgetter('section')):
266 self
.__print
_section
(option
, 0, False)
268 self
.__print
_option
(option
, 0, False)
271 """Formats a list of options into a groff snippet"""
273 self
.__wrapper
= TextWrapper(width
= 80, replace_whitespace
= False,
274 break_long_words
= False, break_on_hyphens
= False)
275 self
.__tags
= GroffTagReplacer()
277 def __groffize(self
, text
):
278 """Encode text as groff text"""
279 text
= self
.__tags
.replace(text
)
280 text
= re
.sub(r
'(?<!\\)-', r
'\\-', text
)
281 # remove any leading whitespace
282 return re
.sub(r
'^\s+', '', text
, flags
= re
.MULTILINE
)
284 def __format_option(self
, option
):
285 """Print a single option"""
286 if option
.section
and not len(option
.desc
):
289 print('.TP\n.B {0}\n.br'.format(option
.fullname
))
292 default
= option
.default
if option
.default
else ''
293 print('.BR {0} " [{1}]"'.format(option
.fullname
, default
))
294 for para
in option
.desc
if len(option
.desc
) < 2 else option
.desc
[1:]:
295 print(self
.__groffize
(self
.__wrapper
.fill(para
)))
298 def format(self
, options
):
299 """Print a list of options"""
302 for option
in options
:
304 self
.__format
_option
(option
)
305 self
.format(option
.options
)
307 self
.__format
_option
(option
)
309 options
= OptionParser(usage
= "Usage: %prog [options] file1 file2\n\n"
310 "If no filenames are provided the input is read from stdin.")
311 options
.add_option("-f", "--format", dest
="format", type="choice", choices
=["conf", "man"],
312 help="output format: conf, man [default: %default]", default
="conf")
313 options
.add_option("-r", "--root", dest
="root", metavar
="NAME",
314 help="root section of which options are printed, "
315 "if not found everything is printed")
316 options
.add_option("-n", "--nosort", action
="store_false", dest
="sort",
317 default
=True, help="do not sort sections alphabetically")
319 (opts
, args
) = options
.parse_args()
321 parser
= Parser(opts
.sort
)
323 for filename
in args
:
325 with
open(filename
, 'r') as file:
328 sys
.stderr
.write("Unable to open '{0}': {1}\n".format(filename
, e
.strerror
))
330 parser
.parse(sys
.stdin
)
332 options
= parser
.options
334 root
= parser
.get_option(opts
.root
)
336 options
= root
.options
338 if opts
.format
== "conf":
339 formatter
= ConfFormatter()
340 elif opts
.format
== "man":
341 formatter
= ManFormatter()
343 formatter
.format(options
)