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