1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
|
# Copyright (C) 2012 Yahoo! Inc.
#
# Author: Joshua Harlow <harlowja@yahoo-inc.com>
#
# This file is part of cloud-init. See LICENSE file for license information.
import pipes
import re
from io import StringIO
# This library is used to parse/write
# out the various sysconfig files edited (best attempt effort)
#
# It has to be slightly modified though
# to ensure that all values are quoted/unquoted correctly
# since these configs are usually sourced into
# bash scripts...
import configobj
# See: http://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap08.html
# or look at the 'param_expand()' function in the subst.c file in the bash
# source tarball...
SHELL_VAR_RULE = r'[a-zA-Z_]+[a-zA-Z0-9_]*'
SHELL_VAR_REGEXES = [
# Basic variables
re.compile(r"\$" + SHELL_VAR_RULE),
# Things like $?, $0, $-, $@
re.compile(r"\$[0-9#\?\-@\*]"),
# Things like ${blah:1} - but this one
# gets very complex so just try the
# simple path
re.compile(r"\$\{.+\}"),
]
def _contains_shell_variable(text):
for r in SHELL_VAR_REGEXES:
if r.search(text):
return True
return False
class SysConf(configobj.ConfigObj):
"""A configobj.ConfigObj subclass specialised for sysconfig files.
:param contents:
The sysconfig file to parse, in a format accepted by
``configobj.ConfigObj.__init__`` (i.e. "a filename, file like object,
or list of lines").
"""
def __init__(self, contents):
configobj.ConfigObj.__init__(self, contents,
interpolation=False,
write_empty_values=True)
def __str__(self):
contents = self.write()
out_contents = StringIO()
if isinstance(contents, (list, tuple)):
out_contents.write("\n".join(contents))
else:
out_contents.write(str(contents))
return out_contents.getvalue()
def _quote(self, value, multiline=False):
if not isinstance(value, str):
raise ValueError('Value "%s" is not a string' % (value))
if len(value) == 0:
return ''
quot_func = None
if value[0] in ['"', "'"] and value[-1] in ['"', "'"]:
if len(value) == 1:
quot_func = (lambda x: self._get_single_quote(x) % x)
else:
# Quote whitespace if it isn't the start + end of a shell command
if value.strip().startswith("$(") and value.strip().endswith(")"):
pass
else:
if re.search(r"[\t\r\n ]", value):
if _contains_shell_variable(value):
# If it contains shell variables then we likely want to
# leave it alone since the pipes.quote function likes
# to use single quotes which won't get expanded...
if re.search(r"[\n\"']", value):
quot_func = (lambda x:
self._get_triple_quote(x) % x)
else:
quot_func = (lambda x:
self._get_single_quote(x) % x)
else:
quot_func = pipes.quote
if not quot_func:
return value
return quot_func(value)
def _write_line(self, indent_string, entry, this_entry, comment):
# Ensure it is formatted fine for
# how these sysconfig scripts are used
val = self._decode_element(self._quote(this_entry))
key = self._decode_element(self._quote(entry))
cmnt = self._decode_element(comment)
return '%s%s%s%s%s' % (indent_string,
key,
self._a_to_u('='),
val,
cmnt)
# vi: ts=4 expandtab
|