diff --git a/setup.py b/setup.py index 73909e8..7cd1458 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ 'submit50': [('**.py', 'python', None),], }, description="This is submit50, with which you can submit solutions to problems for CS50.", - install_requires=["lib50>=2,<3", "requests>=2.19", "termcolor>=1.1"], + install_requires=["lib50>=3,<4", "requests>=2.19", "termcolor>=1.1"], keywords=["submit", "submit50"], name="submit50", python_requires=">=3.6", @@ -25,6 +25,6 @@ entry_points={ "console_scripts": ["submit50=submit50.__main__:main"] }, - version="3.0.4", + version="3.1.0", include_package_data=True ) diff --git a/submit50/__main__.py b/submit50/__main__.py index 2d4defb..a98d5bb 100644 --- a/submit50/__main__.py +++ b/submit50/__main__.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -import os import argparse +import enum import gettext import logging import pkg_resources @@ -21,6 +21,29 @@ SUBMIT_URL = "https://submit.cs50.io" +class LogLevel(enum.IntEnum): + DEBUG = logging.DEBUG + INFO = logging.INFO + WARNING = logging.WARNING + ERROR = logging.ERROR + + +class ColoredFormatter(logging.Formatter): + COLORS = { + "ERROR": "red", + "WARNING": "yellow", + "DEBUG": "cyan", + "INFO": "magenta", + } + + def __init__(self, fmt, use_color=True): + super().__init__(fmt=fmt) + self.use_color = use_color + + def format(self, record): + msg = super().format(record) + return msg if not self.use_color else termcolor.colored(msg, getattr(record, "color", self.COLORS.get(record.levelname))) + class Error(Exception): pass @@ -50,6 +73,30 @@ def check_version(): "Please upgrade.")) +def setup_logging(level): + """ + Sets up logging for lib50. + level 'info' logs all git commands run to stderr + level 'debug' logs all git commands and their output to stderr + """ + logger = logging.getLogger("lib50") + + # Set verbosity level on the lib50 logger + logger.setLevel(level.upper()) + + handler = logging.StreamHandler(sys.stderr) + handler.setFormatter(ColoredFormatter("(%(levelname)s) %(message)s", use_color=sys.stderr.isatty())) + + # Direct all logs to sys.stderr + logger.addHandler(handler) + + # Don't animate the progressbar if LogLevel is either info or debug + lib50.ProgressBar.DISABLED = logger.level < LogLevel.WARNING + + # Show exceptions when debugging + excepthook.verbose = logger.level == LogLevel.DEBUG + + def cprint(text="", color=None, on_color=None, attrs=None, **kwargs): """Colorizes text (and wraps to terminal's width).""" # Assume 80 in case not running in a terminal @@ -62,7 +109,7 @@ def cprint(text="", color=None, on_color=None, attrs=None, **kwargs): color=color, on_color=on_color, attrs=attrs, **kwargs) -def prompt(included, excluded): +def prompt(honesty, included, excluded): if included: cprint(_("Files that will be submitted:"), "green") for file in included: @@ -76,16 +123,33 @@ def prompt(included, excluded): for other in excluded: cprint("./{}".format(other), "yellow") + # If there's no honesty question, continue. + if not honesty: + return True + # Prompt for honesty try: - answer = input(_("Keeping in mind the course's policy on academic honesty, " - "are you sure you want to submit these files (yes/no)? ")) + # Show default message + if honesty == True: + honesty_question = _( + "Keeping in mind the course's policy on academic honesty, " + "are you sure you want to submit these files (yes/no)? " + ) + # If a custom message is configured, show that instead + else: + honesty_question = str(honesty) + + # Get the user's answer + answer = input(honesty_question) except EOFError: answer = None print() + + # If no answer given, or yes is not given, don't continue if not answer or not re.match(f"^\s*(?:{_('y|yes')})\s*$", answer, re.I): return False - + + # Otherwise, do continue return True @@ -102,9 +166,9 @@ def excepthook(type, value, tb): cprint(_("Submission cancelled."), "red") - excepthook.verbose = True + class LogoutAction(argparse.Action): def __init__(self, option_strings, dest=argparse.SUPPRESS, default=argparse.SUPPRESS, help=_("logout of submit50")): super().__init__(option_strings, dest=dest, nargs=0, default=default, help=help) @@ -124,20 +188,29 @@ def main(): parser = argparse.ArgumentParser(prog="submit50") parser.add_argument("--logout", action=LogoutAction) - parser.add_argument("-v", "--verbose", - action="store_true", - help=_("show commands being executed")) - parser.add_argument("-V", "--version", action="version", version=f"%(prog)s {__version__}") - parser.add_argument("slug", help=_( - "prescribed identifier of work to submit")) + parser.add_argument( + "--log-level", + action="store", + default="warning", + choices=[level.name.lower() for level in LogLevel], + type=str.lower, + help=_('warning: displays usage warnings.' + '\ninfo: adds all commands run.' + '\ndebug: adds the output of all commands run.') + ) + parser.add_argument( + "-V", "--version", + action="version", + version=f"%(prog)s {__version__}" + ) + parser.add_argument( + "slug", + help=_("prescribed identifier of work to submit") + ) args = parser.parse_args() - excepthook.verbose = args.verbose - if args.verbose: - logging.basicConfig(level=os.environ.get("SUBMIT50_LOGLEVEL", "INFO")) - # Disable progress bar so it doesn't interfere with log - lib50.ProgressBar.DISABLED = True + setup_logging(args.log_level) check_announcements() check_version()