11#!/usr/bin/env python3
2- import os
32import argparse
3+ import enum
44import gettext
55import logging
66import pkg_resources
2121
2222SUBMIT_URL = "https://submit.cs50.io"
2323
24+ class LogLevel (enum .IntEnum ):
25+ DEBUG = logging .DEBUG
26+ INFO = logging .INFO
27+ WARNING = logging .WARNING
28+ ERROR = logging .ERROR
29+
30+
31+ class ColoredFormatter (logging .Formatter ):
32+ COLORS = {
33+ "ERROR" : "red" ,
34+ "WARNING" : "yellow" ,
35+ "DEBUG" : "cyan" ,
36+ "INFO" : "magenta" ,
37+ }
38+
39+ def __init__ (self , fmt , use_color = True ):
40+ super ().__init__ (fmt = fmt )
41+ self .use_color = use_color
42+
43+ def format (self , record ):
44+ msg = super ().format (record )
45+ return msg if not self .use_color else termcolor .colored (msg , getattr (record , "color" , self .COLORS .get (record .levelname )))
46+
2447
2548class Error (Exception ):
2649 pass
@@ -50,6 +73,30 @@ def check_version():
5073 "Please upgrade." ))
5174
5275
76+ def setup_logging (level ):
77+ """
78+ Sets up logging for lib50.
79+ level 'info' logs all git commands run to stderr
80+ level 'debug' logs all git commands and their output to stderr
81+ """
82+ logger = logging .getLogger ("lib50" )
83+
84+ # Set verbosity level on the lib50 logger
85+ logger .setLevel (level .upper ())
86+
87+ handler = logging .StreamHandler (sys .stderr )
88+ handler .setFormatter (ColoredFormatter ("(%(levelname)s) %(message)s" , use_color = sys .stderr .isatty ()))
89+
90+ # Direct all logs to sys.stderr
91+ logger .addHandler (handler )
92+
93+ # Don't animate the progressbar if LogLevel is either info or debug
94+ lib50 .ProgressBar .DISABLED = logger .level < LogLevel .WARNING
95+
96+ # Show exceptions when debugging
97+ excepthook .verbose = logger .level == LogLevel .DEBUG
98+
99+
53100def cprint (text = "" , color = None , on_color = None , attrs = None , ** kwargs ):
54101 """Colorizes text (and wraps to terminal's width)."""
55102 # Assume 80 in case not running in a terminal
@@ -62,7 +109,7 @@ def cprint(text="", color=None, on_color=None, attrs=None, **kwargs):
62109 color = color , on_color = on_color , attrs = attrs , ** kwargs )
63110
64111
65- def prompt (included , excluded ):
112+ def prompt (honesty , included , excluded ):
66113 if included :
67114 cprint (_ ("Files that will be submitted:" ), "green" )
68115 for file in included :
@@ -76,16 +123,33 @@ def prompt(included, excluded):
76123 for other in excluded :
77124 cprint ("./{}" .format (other ), "yellow" )
78125
126+ # If there's no honesty question, continue.
127+ if not honesty :
128+ return True
129+
79130 # Prompt for honesty
80131 try :
81- answer = input (_ ("Keeping in mind the course's policy on academic honesty, "
82- "are you sure you want to submit these files (yes/no)? " ))
132+ # Show default message
133+ if honesty == True :
134+ honesty_question = _ (
135+ "Keeping in mind the course's policy on academic honesty, "
136+ "are you sure you want to submit these files (yes/no)? "
137+ )
138+ # If a custom message is configured, show that instead
139+ else :
140+ honesty_question = str (honesty )
141+
142+ # Get the user's answer
143+ answer = input (honesty_question )
83144 except EOFError :
84145 answer = None
85146 print ()
147+
148+ # If no answer given, or yes is not given, don't continue
86149 if not answer or not re .match (f"^\s*(?:{ _ ('y|yes' )} )\s*$" , answer , re .I ):
87150 return False
88-
151+
152+ # Otherwise, do continue
89153 return True
90154
91155
@@ -102,9 +166,9 @@ def excepthook(type, value, tb):
102166
103167 cprint (_ ("Submission cancelled." ), "red" )
104168
105-
106169excepthook .verbose = True
107170
171+
108172class LogoutAction (argparse .Action ):
109173 def __init__ (self , option_strings , dest = argparse .SUPPRESS , default = argparse .SUPPRESS , help = _ ("logout of submit50" )):
110174 super ().__init__ (option_strings , dest = dest , nargs = 0 , default = default , help = help )
@@ -124,20 +188,29 @@ def main():
124188
125189 parser = argparse .ArgumentParser (prog = "submit50" )
126190 parser .add_argument ("--logout" , action = LogoutAction )
127- parser .add_argument ("-v" , "--verbose" ,
128- action = "store_true" ,
129- help = _ ("show commands being executed" ))
130- parser .add_argument ("-V" , "--version" , action = "version" , version = f"%(prog)s { __version__ } " )
131- parser .add_argument ("slug" , help = _ (
132- "prescribed identifier of work to submit" ))
191+ parser .add_argument (
192+ "--log-level" ,
193+ action = "store" ,
194+ default = "warning" ,
195+ choices = [level .name .lower () for level in LogLevel ],
196+ type = str .lower ,
197+ help = _ ('warning: displays usage warnings.'
198+ '\n info: adds all commands run.'
199+ '\n debug: adds the output of all commands run.' )
200+ )
201+ parser .add_argument (
202+ "-V" , "--version" ,
203+ action = "version" ,
204+ version = f"%(prog)s { __version__ } "
205+ )
206+ parser .add_argument (
207+ "slug" ,
208+ help = _ ("prescribed identifier of work to submit" )
209+ )
133210
134211 args = parser .parse_args ()
135212
136- excepthook .verbose = args .verbose
137- if args .verbose :
138- logging .basicConfig (level = os .environ .get ("SUBMIT50_LOGLEVEL" , "INFO" ))
139- # Disable progress bar so it doesn't interfere with log
140- lib50 .ProgressBar .DISABLED = True
213+ setup_logging (args .log_level )
141214
142215 check_announcements ()
143216 check_version ()
0 commit comments