• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2021 The Pigweed Authors
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may not
4# use this file except in compliance with the License. You may obtain a copy of
5# the License at
6#
7#     https://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations under
13# the License.
14"""Serializes an Environment into a shell file."""
15
16import inspect
17
18# Disable super() warnings since this file must be Python 2 compatible.
19# pylint: disable=super-with-arguments
20
21
22class _BaseShellVisitor(object):  # pylint: disable=useless-object-inheritance
23    def __init__(self, *args, **kwargs):
24        pathsep = kwargs.pop('pathsep', ':')
25        super(_BaseShellVisitor, self).__init__(*args, **kwargs)
26        self._pathsep = pathsep
27        self._outs = None
28
29    def _remove_value_from_path(self, variable, value):
30        return (
31            '{variable}="$(echo "${variable}"'
32            ' | sed "s|{pathsep}{value}{pathsep}|{pathsep}|g;"'
33            ' | sed "s|^{value}{pathsep}||g;"'
34            ' | sed "s|{pathsep}{value}$||g;"'
35            ')"\nexport {variable}\n'.format(
36                variable=variable, value=value, pathsep=self._pathsep
37            )
38        )
39
40    def visit_hash(self, hash):  # pylint: disable=redefined-builtin
41        del hash
42        self._outs.write(
43            inspect.cleandoc(
44                '''
45        # This should detect bash and zsh, which have a hash command that must
46        # be called to get it to forget past commands. Without forgetting past
47        # commands the $PATH changes we made may not be respected.
48        if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
49            hash -r\n
50        fi
51        '''
52            )
53        )
54
55
56class ShellVisitor(_BaseShellVisitor):
57    """Serializes an Environment into a shell file."""
58
59    def __init__(self, *args, **kwargs):
60        super(ShellVisitor, self).__init__(*args, **kwargs)
61        self._replacements = ()
62
63    def serialize(self, env, outs):
64        try:
65            self._replacements = tuple(
66                (key, env.get(key) if value is None else value)
67                for key, value in env.replacements
68            )
69            self._outs = outs
70
71            env.accept(self)
72
73        finally:
74            self._replacements = ()
75            self._outs = None
76
77    def _apply_replacements(self, action):
78        value = action.value
79        for var, replacement in self._replacements:
80            if var != action.name:
81                value = value.replace(replacement, '${}'.format(var))
82        return value
83
84    def visit_set(self, set):  # pylint: disable=redefined-builtin
85        value = self._apply_replacements(set)
86        self._outs.write(
87            '{name}="{value}"\nexport {name}\n'.format(
88                name=set.name, value=value
89            )
90        )
91
92    def visit_clear(self, clear):
93        self._outs.write('unset {name}\n'.format(**vars(clear)))
94
95    def visit_remove(self, remove):
96        value = self._apply_replacements(remove)
97        self._outs.write(
98            '# Remove \n#   {value}\n# from {name} before adding it '
99            'back.\n'.format(value=remove.value, name=remove.name)
100        )
101        self._outs.write(self._remove_value_from_path(remove.name, value))
102
103    def _join(self, *args):
104        if len(args) == 1 and isinstance(args[0], (list, tuple)):
105            args = args[0]
106        return self._pathsep.join(args)
107
108    def visit_prepend(self, prepend):
109        value = self._apply_replacements(prepend)
110        value = self._join(value, '${}'.format(prepend.name))
111        self._outs.write(
112            '{name}="{value}"\nexport {name}\n'.format(
113                name=prepend.name, value=value
114            )
115        )
116
117    def visit_append(self, append):
118        value = self._apply_replacements(append)
119        value = self._join('${}'.format(append.name), value)
120        self._outs.write(
121            '{name}="{value}"\nexport {name}\n'.format(
122                name=append.name, value=value
123            )
124        )
125
126    def visit_echo(self, echo):
127        # TODO(mohrr) use shlex.quote().
128        self._outs.write('if [ -z "${PW_ENVSETUP_QUIET:-}" ]; then\n')
129        if echo.newline:
130            self._outs.write('  echo "{}"\n'.format(echo.value))
131        else:
132            self._outs.write('  echo -n "{}"\n'.format(echo.value))
133        self._outs.write('fi\n')
134
135    def visit_comment(self, comment):
136        for line in comment.value.splitlines():
137            self._outs.write('# {}\n'.format(line))
138
139    def visit_command(self, command):
140        # TODO(mohrr) use shlex.quote here?
141        self._outs.write('{}\n'.format(' '.join(command.command)))
142        if not command.exit_on_error:
143            return
144
145        # Assume failing command produced relevant output.
146        self._outs.write('if [ "$?" -ne 0 ]; then\n  return 1\nfi\n')
147
148    def visit_doctor(self, doctor):
149        self._outs.write('if [ -z "$PW_ACTIVATE_SKIP_CHECKS" ]; then\n')
150        self.visit_command(doctor)
151        self._outs.write('else\n')
152        self._outs.write(
153            'echo Skipping environment check because '
154            'PW_ACTIVATE_SKIP_CHECKS is set\n'
155        )
156        self._outs.write('fi\n')
157
158    def visit_blank_line(self, blank_line):
159        del blank_line
160        self._outs.write('\n')
161
162    def visit_function(self, function):
163        self._outs.write(
164            '{name}() {{\n{body}\n}}\n'.format(
165                name=function.name, body=function.body
166            )
167        )
168
169
170class DeactivateShellVisitor(_BaseShellVisitor):
171    """Removes values from an Environment."""
172
173    def __init__(self, *args, **kwargs):
174        pathsep = kwargs.pop('pathsep', ':')
175        super(DeactivateShellVisitor, self).__init__(*args, **kwargs)
176        self._pathsep = pathsep
177
178    def serialize(self, env, outs):
179        try:
180            self._outs = outs
181
182            env.accept(self)
183
184        finally:
185            self._outs = None
186
187    def visit_set(self, set):  # pylint: disable=redefined-builtin
188        if set.deactivate:
189            self._outs.write('unset {name}\n'.format(name=set.name))
190
191    def visit_clear(self, clear):
192        pass  # Not relevant.
193
194    def visit_remove(self, remove):
195        pass  # Not relevant.
196
197    def visit_prepend(self, prepend):
198        self._outs.write(
199            self._remove_value_from_path(prepend.name, prepend.value)
200        )
201
202    def visit_append(self, append):
203        self._outs.write(
204            self._remove_value_from_path(append.name, append.value)
205        )
206
207    def visit_echo(self, echo):
208        pass  # Not relevant.
209
210    def visit_comment(self, comment):
211        pass  # Not relevant.
212
213    def visit_command(self, command):
214        pass  # Not relevant.
215
216    def visit_doctor(self, doctor):
217        pass  # Not relevant.
218
219    def visit_blank_line(self, blank_line):
220        pass  # Not relevant.
221
222    def visit_function(self, function):
223        pass  # Not relevant.
224