Add --respond option to pg.py's mailpipe command.
[pygrader.git] / bin / pg.py
1 #!/usr/bin/env python3
2 #
3 # Copyright (C) 2012 W. Trevor King <wking@drexel.edu>
4 #
5 # This file is part of pygrader.
6 #
7 # pygrader is free software: you can redistribute it and/or modify it under the
8 # terms of the GNU General Public License as published by the Free Software
9 # Foundation, either version 3 of the License, or (at your option) any later
10 # version.
11 #
12 # pygrader is distributed in the hope that it will be useful, but WITHOUT ANY
13 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License along with
17 # pygrader.  If not, see <http://www.gnu.org/licenses/>.
18
19 """Manage grades from the command line
20 """
21
22 import configparser as _configparser
23 from email.mime.text import MIMEText as _MIMEText
24 import email.utils as _email_utils
25 import inspect as _inspect
26 import logging as _logging
27 import os.path as _os_path
28 import sys as _sys
29
30 import pgp_mime as _pgp_mime
31
32 from pygrader import __version__
33 from pygrader import LOG as _LOG
34 from pygrader.email import test_smtp as _test_smtp
35 from pygrader.email import Responder as _Responder
36 from pygrader.mailpipe import mailpipe as _mailpipe
37 from pygrader.storage import initialize as _initialize
38 from pygrader.storage import load_course as _load_course
39 from pygrader.tabulate import tabulate as _tabulate
40 from pygrader.template import assignment_email as _assignment_email
41 from pygrader.template import course_email as _course_email
42 from pygrader.template import student_email as _student_email
43 from pygrader.todo import print_todo as _todo
44
45
46 if __name__ == '__main__':
47     from argparse import ArgumentParser as _ArgumentParser
48
49     parser = _ArgumentParser(
50         description=__doc__, version=__version__)
51     parser.add_argument(
52         '-d', '--base-dir', dest='basedir', default='.',
53         help='Base directory containing grade data')
54     parser.add_argument(
55         '-c', '--color', default=False, action='store_const', const=True,
56         help='Color printed output with ANSI escape sequences')
57     parser.add_argument(
58         '-V', '--verbose', default=0, action='count',
59         help='Increase verbosity')
60     subparsers = parser.add_subparsers(title='commands')
61
62     smtp_parser = subparsers.add_parser(
63         'smtp', help=_test_smtp.__doc__.splitlines()[0])
64     smtp_parser.set_defaults(func=_test_smtp)
65     smtp_parser.add_argument(
66         '-a', '--author',
67         help='Your address (email author)')
68     smtp_parser.add_argument(
69         '-t', '--target', dest='targets', action='append',
70         help='Address for the email recipient')
71
72     initialize_parser = subparsers.add_parser(
73         'initialize', help=_initialize.__doc__.splitlines()[0])
74     initialize_parser.set_defaults(func=_initialize)
75     initialize_parser.add_argument(
76         '-D', '--dry-run', default=False, action='store_const', const=True,
77         help="Don't actually send emails, create files, etc.")
78
79     tabulate_parser = subparsers.add_parser(
80         'tabulate', help=_tabulate.__doc__.splitlines()[0])
81     tabulate_parser.set_defaults(func=_tabulate)
82     tabulate_parser.add_argument(
83         '-s', '--statistics', default=False, action='store_const', const=True,
84         help='Calculate mean and standard deviation for each assignment')
85
86     email_parser = subparsers.add_parser(
87         'email', help='Send emails containing grade information')
88     email_parser.add_argument(
89         '-D', '--dry-run', default=False, action='store_const', const=True,
90         help="Don't actually send emails, create files, etc.")
91     email_parser.add_argument(
92         '-a', '--author',
93         help='Your name (email author), defaults to course robot')
94     email_parser.add_argument(
95         '--cc', action='append', help='People to carbon copy')
96     email_subparsers = email_parser.add_subparsers(title='type')
97     assignment_parser = email_subparsers.add_parser(
98         'assignment', help=_assignment_email.__doc__.splitlines()[0])
99     assignment_parser.set_defaults(func=_assignment_email)
100     assignment_parser.add_argument(
101         'assignment', help='Name of the target assignment')
102     student_parser = email_subparsers.add_parser(
103         'student', help=_student_email.__doc__.splitlines()[0])
104     student_parser.set_defaults(func=_student_email)
105     student_parser.add_argument(
106         '-o', '--old', default=False, action='store_const', const=True,
107         help='Include already-notified information in emails')
108     student_parser.add_argument(
109         '-s', '--student', dest='student',
110         help='Explicitly select the student to notify (instead of everyone)')
111     course_parser = email_subparsers.add_parser(
112         'course', help=_course_email.__doc__.splitlines()[0])
113     course_parser.set_defaults(func=_course_email)
114     course_parser.add_argument(
115         '-t', '--target', dest='targets', action='append',
116         help='Name, alias, or group for the email recipient(s)')
117
118     mailpipe_parser = subparsers.add_parser(
119         'mailpipe', help=_mailpipe.__doc__.splitlines()[0])
120     mailpipe_parser.set_defaults(func=_mailpipe)
121     mailpipe_parser.add_argument(
122         '-D', '--dry-run', default=False, action='store_const', const=True,
123         help="Don't actually send emails, create files, etc.")
124     mailpipe_parser.add_argument(
125         '-m', '--mailbox', choices=['maildir', 'mbox'],
126         help=('Instead of piping a message in via stdout, you can also read '
127               'directly from a mailbox.  This option specifies the format of '
128               'your target mailbox.'))
129     mailpipe_parser.add_argument(
130         '-i', '--input', dest='input_', metavar='INPUT',
131         help='Path to the mailbox containing messages to be processed')
132     mailpipe_parser.add_argument(
133         '-o', '--output',
134         help=('Path to the mailbox that will recieve successfully processed '
135               'messages.  If not given, successfully processed messages will '
136               'be left in the input mailbox'))
137     mailpipe_parser.add_argument(
138         '-l', '--max-late', default=0, type=float,
139         help=('Grace period in seconds before an incoming assignment is '
140               'actually marked as late'))
141     mailpipe_parser.add_argument(
142         '-r', '--respond', default=False, action='store_const', const=True,
143         help=('Send automatic response emails to acknowledge incomming '
144               'messages.'))
145
146     todo_parser = subparsers.add_parser(
147         'todo', help=_todo.__doc__.splitlines()[0])
148     todo_parser.set_defaults(func=_todo)
149     todo_parser.add_argument(
150         'source', help='Name of source file/directory')
151     todo_parser.add_argument(
152         'target', help='Name of target file/directory')
153
154
155 #    p.add_option('-t', '--template', default=None)
156
157     args = parser.parse_args()
158
159     if args.verbose:
160         _LOG.setLevel(max(_logging.DEBUG, _LOG.level - 10*args.verbose))
161         _pgp_mime.LOG.setLevel(_LOG.level)
162
163     config = _configparser.ConfigParser()
164     config.read([
165             _os_path.expanduser(_os_path.join('~', '.config', 'smtplib.conf')),
166             ])
167
168     func_args = _inspect.getargspec(args.func).args
169     kwargs = {}
170
171     if 'basedir' in func_args:
172         kwargs['basedir'] = args.basedir
173
174     if 'course' in func_args:
175         course = _load_course(basedir=args.basedir)
176         active_groups = course.active_groups()
177         kwargs['course'] = course
178         if hasattr(args, 'assignment'):
179             kwargs['assignment'] = course.assignment(name=args.assignment)
180         if hasattr(args, 'cc') and args.cc:
181             kwargs['cc'] = [course.person(name=cc) for cc in args.cc]
182         for attr in ['author', 'student']:
183             if hasattr(args, attr):
184                 name = getattr(args, attr)
185                 if name is None and attr == 'author':
186                     kwargs[attr] = course.robot
187                 else:
188                     kwargs[attr] = course.person(name=name)
189         for attr in ['targets']:
190             if hasattr(args, attr):
191                 people = getattr(args, attr)
192                 if people is None:
193                     people = ['professors']  # for the course email
194                 kwargs[attr] = []
195                 for person in people:
196                     if person in active_groups:
197                         kwargs[attr].extend(course.find_people(group=person))
198                     else:
199                         kwargs[attr].extend(course.find_people(name=person))
200         for attr in ['dry_run', 'mailbox', 'output', 'input_', 'max_late',
201                      'old', 'statistics']:
202             if hasattr(args, attr):
203                 kwargs[attr] = getattr(args, attr)
204     elif args.func == _test_smtp:
205         for attr in ['author', 'targets']:
206             if hasattr(args, attr):
207                 kwargs[attr] = getattr(args, attr)
208     elif args.func == _todo:
209         for attr in ['source', 'target']:
210             if hasattr(args, attr):
211                 kwargs[attr] = getattr(args, attr)
212
213     if 'use_color' in func_args:
214         kwargs['use_color'] = args.color
215
216     if ('smtp' in func_args and
217         not kwargs.get('dry_run', False) and
218         'smtp' in config.sections()):
219         params = _pgp_mime.get_smtp_params(config)
220         kwargs['smtp'] = _pgp_mime.get_smtp(*params)
221         del params
222
223     if hasattr(args, 'respond') and getattr(args, 'respond'):
224         kwargs['respond'] = _Responder(
225             smtp=kwargs.get('smtp', None),
226             use_color=kwargs.get('use_color', False),
227             dry_run=kwargs.get('dry_run', False))
228
229     _LOG.debug('execute {} with {}'.format(args.func, kwargs))
230     try:
231         ret = args.func(**kwargs)
232     finally:
233         smtp = kwargs.get('smtp', None)
234         if smtp:
235             _LOG.info('disconnect from SMTP server')
236             smtp.quit()
237     if ret is None:
238         ret = 0
239     _sys.exit(ret)