summaryrefslogtreecommitdiff
path: root/cloudinit/distros/parsers/sys_conf.py
blob: dee4c551d6515ea8eeec88d9861163208e217cd7 (plain)
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