source: rtems-tools/rtemstoolkit/mailer.py

Last change on this file was 6b31bb0, checked in by Alex White <alex.white@…>, on 11/12/21 at 14:25:48

rtemstoolkit/mailer.py: Fix parsing of options with no optarg

  • Property mode set to 100644
File size: 8.9 KB
Line 
1#
2# RTEMS Tools Project (http://www.rtems.org/)
3# Copyright 2013-2016 Chris Johns (chrisj@rtems.org)
4# Copyright (C) 2021 On-Line Applications Research Corporation (OAR)
5# All rights reserved.
6#
7# This file is part of the RTEMS Tools package in 'rtems-tools'.
8#
9# Redistribution and use in source and binary forms, with or without
10# modification, are permitted provided that the following conditions are met:
11#
12# 1. Redistributions of source code must retain the above copyright notice,
13# this list of conditions and the following disclaimer.
14#
15# 2. Redistributions in binary form must reproduce the above copyright notice,
16# this list of conditions and the following disclaimer in the documentation
17# and/or other materials provided with the distribution.
18#
19# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29# POSSIBILITY OF SUCH DAMAGE.
30#
31
32#
33# Manage emailing results or reports.
34#
35
36from __future__ import print_function
37
38import os
39import smtplib
40import socket
41
42from rtemstoolkit import error
43from rtemstoolkit import execute
44from rtemstoolkit import options
45from rtemstoolkit import path
46
47_options = {
48    '--mail'         : 'Send email report or results.',
49    '--use-gitconfig': 'Use mail configuration from git config.',
50    '--mail-to'      : 'Email address to send the email to.',
51    '--mail-from'    : 'Email address the report is from.',
52    '--smtp-host'    : 'SMTP host to send via.',
53    '--smtp-port'    : 'SMTP port to send via.',
54    '--smtp-user'    : 'User for SMTP authentication.',
55    '--smtp-password': 'Password for SMTP authentication.'
56}
57
58def append_options(opts):
59    for o in _options:
60        opts[o] = _options[o]
61
62def add_arguments(argsp):
63    argsp.add_argument('--mail', help = _options['--mail'], action = 'store_true')
64    argsp.add_argument('--use-gitconfig', help = _options['--use-gitconfig'], action = 'store_true')
65    no_add = ['--mail', '--use-gitconfig']
66    for o in [opt for opt in list(_options) if opt not in no_add]:
67        argsp.add_argument(o, help = _options[o], type = str)
68
69class mail:
70    def __init__(self, opts):
71        self.opts = opts
72        self.gitconfig_lines = None
73        if self._get_arg('--use-gitconfig'):
74            # Read the output of `git config --list` instead of reading the
75            # .gitconfig file directly because Python 2 ConfigParser does not
76            # accept tabs at the beginning of lines.
77            e = execute.capture_execution()
78            exit_code, proc, output = e.open('git config --list', shell=True)
79            if exit_code == 0:
80                self.gitconfig_lines = output.split(os.linesep)
81
82    def _args_are_macros(self):
83        return isinstance(self.opts, options.command_line)
84
85    def _get_arg(self, arg):
86        if self._args_are_macros():
87            value = self.opts.find_arg(arg)
88            if value is not None:
89                if len(value) > 1:
90                    value = self.opts.find_arg(arg)[1]
91                else:
92                    value = True
93        else:
94            if arg.startswith('--'):
95                arg = arg[2:]
96            arg = arg.replace('-', '_')
97            if arg in vars(self.opts):
98                value = vars(self.opts)[arg]
99            else:
100                value = None
101        return value
102
103    def _get_from_gitconfig(self, variable_name):
104        if self.gitconfig_lines is None:
105            return None
106
107        for line in self.gitconfig_lines:
108            if line.startswith(variable_name):
109                ls = line.split('=')
110                if len(ls) >= 2:
111                    return ls[1]
112
113    def from_address(self):
114
115        def _clean(l):
116            if '#' in l:
117                l = l[:l.index('#')]
118            if '\r' in l:
119                l = l[:l.index('r')]
120            if '\n' in l:
121                l = l[:l.index('\n')]
122            return l.strip()
123
124        addr = self._get_arg('--mail-from')
125        if addr is not None:
126            return addr
127        addr = self._get_from_gitconfig('user.email')
128        if addr is not None:
129            name = self._get_from_gitconfig('user.name')
130            if name is not None:
131                addr = '%s <%s>' % (name, addr)
132            return addr
133        mailrc = None
134        if 'MAILRC' in os.environ:
135            mailrc = os.environ['MAILRC']
136        if mailrc is None and 'HOME' in os.environ:
137            mailrc = path.join(os.environ['HOME'], '.mailrc')
138        if mailrc is not None and path.exists(mailrc):
139            # set from="Joe Blow <joe@blow.org>"
140            try:
141                with open(mailrc, 'r') as mrc:
142                    lines = mrc.readlines()
143            except IOError as err:
144                raise error.general('error reading: %s' % (mailrc))
145            for l in lines:
146                l = _clean(l)
147                if 'from' in l:
148                    fa = l[l.index('from') + len('from'):]
149                    if '=' in fa:
150                        addr = fa[fa.index('=') + 1:].replace('"', ' ').strip()
151            if addr is not None:
152                return addr
153        if self._args_are_macros():
154            addr = self.opts.defaults.get_value('%{_sbgit_mail}')
155        else:
156            raise error.general('no valid from address for mail')
157        return addr
158
159    def smtp_host(self):
160        host = self._get_arg('--smtp-host')
161        if host is not None:
162            return host
163        host = self._get_from_gitconfig('sendemail.smtpserver')
164        if host is not None:
165            return host
166        if self._args_are_macros():
167            host = self.opts.defaults.get_value('%{_mail_smtp_host}')
168        if host is not None:
169            return host
170        return 'localhost'
171
172    def smtp_port(self):
173        port = self._get_arg('--smtp-port')
174        if port is not None:
175            return port
176        port = self._get_from_gitconfig('sendemail.smtpserverport')
177        if port is not None:
178            return port
179        if self._args_are_macros():
180            port = self.opts.defaults.get_value('%{_mail_smtp_port}')
181        return port
182
183    def smtp_user(self):
184        user = self._get_arg('--smtp-user')
185        if user is not None:
186            return user
187        user = self._get_from_gitconfig('sendemail.smtpuser')
188        return user
189
190    def smtp_password(self):
191        password = self._get_arg('--smtp-password')
192        if password is not None:
193            return password
194        password = self._get_from_gitconfig('sendemail.smtppass')
195        return password
196
197    def send(self, to_addr, subject, body):
198        from_addr = self.from_address()
199        msg = "From: %s\r\nTo: %s\r\nSubject: %s\r\n\r\n" % \
200            (from_addr, to_addr, subject) + body
201        port = self.smtp_port()
202
203        try:
204            s = smtplib.SMTP(self.smtp_host(), port, timeout=10)
205
206            password = self.smtp_password()
207            # If a password is provided, assume that authentication is required.
208            if password is not None:
209                user = self.smtp_user()
210                if user is None:
211                    user = from_addr
212                s.starttls()
213                s.login(user, password)
214
215            s.sendmail(from_addr, [to_addr], msg)
216        except smtplib.SMTPException as se:
217            raise error.general('sending mail: %s' % (str(se)))
218        except socket.error as se:
219            raise error.general('sending mail: %s' % (str(se)))
220
221    def send_file_as_body(self, to_addr, subject, name, intro = None):
222        try:
223            with open(name, 'r') as f:
224                body = f.readlines()
225        except IOError as err:
226            raise error.general('error reading mail body: %s' % (name))
227        if intro is not None:
228            body = intro + body
229        self.send(to_addr, from_addr, body)
230
231if __name__ == '__main__':
232    import sys
233    from rtemstoolkit import macros
234    optargs = {}
235    rtdir = 'rtemstoolkit'
236    defaults = '%s/defaults.mc' % (rtdir)
237    append_options(optargs)
238    opts = options.command_line(base_path = '.',
239                                argv = sys.argv,
240                                optargs = optargs,
241                                defaults = macros.macros(name = defaults, rtdir = rtdir),
242                                command_path = '.')
243    options.load(opts)
244    m = mail(opts)
245    print('From: %s' % (m.from_address()))
246    print('SMTP Host: %s' % (m.smtp_host()))
247    if '--mail' in sys.argv:
248        m.send(m.from_address(), 'Test mailer.py', 'This is a test')
Note: See TracBrowser for help on using the repository browser.