def test_local(self): local_dir = lib50.local("cs50/lib50/tests/bar") self.assertTrue(local_dir.is_dir()) self.assertTrue((local_dir / "__init__.py").is_file()) self.assertTrue((local_dir / ".cs50.yaml").is_file()) local_dir = lib50.local("cs50/lib50/tests/bar") self.assertTrue(local_dir.is_dir()) self.assertTrue((local_dir / "__init__.py").is_file()) self.assertTrue((local_dir / ".cs50.yaml").is_file()) shutil.rmtree(local_dir) local_dir = lib50.local("cs50/lib50/tests/bar") self.assertTrue(local_dir.is_dir()) self.assertTrue((local_dir / "__init__.py").is_file()) self.assertTrue((local_dir / ".cs50.yaml").is_file()) shutil.rmtree(local_dir)
def main(): parser = ArgumentParser(prog="help50", description="A command-line tool that helps " "students understand error messages.") parser.add_argument("-s", "--slug", help="identifier indicating from where to download helpers", default="cs50/helpers/master") parser.add_argument("-d", "--dev", help="slug will be treated as a local path, useful for developing helpers (implies --verbose)", action="store_true") parser.add_argument("-i", "--interactive", help="read command output from stdin instead of running a command", action="store_true") parser.add_argument("-v", "--verbose", help="display the full tracebacks of any errors", action="store_true") parser.add_argument("-V", "--version", action="version", version=f"%(prog)s {__version__}") parser.add_argument("command", nargs=REMAINDER, default=[], help="command to be run") args = parser.parse_args() if args.dev: args.verbose = True excepthook.verbose = args.verbose if args.interactive: script = sys.stdin.read() elif args.command: # Capture stdout and stderr from process, and print it out with tempfile.TemporaryFile(mode="r+b") as temp: env = os.environ.copy() # Hack to prevent some programs from wrapping their error messages env["COLUMNS"] = "5050" proc = pexpect.spawn(f"bash -lc \"{' '.join(shlex.quote(word) for word in args.command)}\"", env=env) proc.logfile_read = temp proc.interact() proc.close() temp.seek(0) script = temp.read().decode().replace("\r\n", "\n") else: raise Error("Careful, you forgot to tell me with which command you " "need help!") termcolor.cprint("\nAsking for help...\n", "yellow") try: helpers_dir = args.slug if args.dev else lib50.local(args.slug) except lib50.Error: raise Error("Failed to fetch helpers, please ensure that you are connected to the internet!") internal.load_helpers(helpers_dir) render_help(internal.get_help(script))
def main(): parser = argparse.ArgumentParser(prog="check50") parser.add_argument("slug", help=_("prescribed identifier of work to check")) parser.add_argument( "-d", "--dev", action="store_true", help= _("run check50 in development mode (implies --offline and --verbose).\n" "causes SLUG to be interpreted as a literal path to a checks package" )) parser.add_argument( "--offline", action="store_true", help=_("run checks completely offline (implies --local)")) parser.add_argument( "-l", "--local", action="store_true", help= _("run checks locally instead of uploading to cs50 (enabled by default in beta version)" )) parser.add_argument( "--log", action="store_true", help=_("display more detailed information about check results")) parser.add_argument("-o", "--output", action="store", default="ansi", choices=["ansi", "json"], help=_("format of check results")) parser.add_argument( "-v", "--verbose", action="store_true", help=_( "display the full tracebacks of any errors (also implies --log)")) parser.add_argument("-V", "--version", action="version", version=f"%(prog)s {__version__}") parser.add_argument("--logout", action=LogoutAction) args = parser.parse_args() # TODO: remove this when submit.cs50.io API is stabilized args.local = True if args.dev: args.offline = True args.verbose = True if args.offline: args.local = True if args.verbose: # Show lib50 commands being run in verbose mode logging.basicConfig(level="INFO") lib50.ProgressBar.DISABLED = True args.log = True excepthook.verbose = args.verbose excepthook.output = args.output if args.local: # If developing, assume slug is a path to check_dir if args.dev: internal.check_dir = Path(args.slug).expanduser().resolve() if not internal.check_dir.is_dir(): raise Error( _("{} is not a directory").format(internal.check_dir)) else: # Otherwise have lib50 create a local copy of slug internal.check_dir = lib50.local(args.slug, "check50", offline=args.offline) config = internal.load_config(internal.check_dir) install_translations(config["translations"]) if not args.offline: install_dependencies(config["dependencies"], verbose=args.verbose) checks_file = (internal.check_dir / config["checks"]).resolve() # Have lib50 decide which files to include included = lib50.files(config.get("files"))[0] if args.verbose: stdout = sys.stdout stderr = sys.stderr else: stdout = stderr = open(os.devnull, "w") # Create a working_area (temp dir) with all included student files named - with lib50.working_area(included, name='-') as working_area, \ contextlib.redirect_stdout(stdout), \ contextlib.redirect_stderr(stderr): results = CheckRunner(checks_file).run(included, working_area) else: # TODO: Remove this before we ship raise NotImplementedError( "cannot run check50 remotely, until version 3.0.0 is shipped ") username, commit_hash = lib50.push("check50", args.slug) results = await_results( f"https://cs50.me/check50/status/{username}/{commit_hash}") if args.output == "json": print_json(results) else: print_ansi(results, log=args.log)
def main(): parser = argparse.ArgumentParser(prog="check50") parser.add_argument("slug", help=_("prescribed identifier of work to check")) parser.add_argument("-d", "--dev", action="store_true", help=_("run check50 in development mode (implies --offline and --verbose).\n" "causes SLUG to be interpreted as a literal path to a checks package")) parser.add_argument("--offline", action="store_true", help=_("run checks completely offline (implies --local)")) parser.add_argument("-l", "--local", action="store_true", help=_("run checks locally instead of uploading to cs50")) parser.add_argument("--log", action="store_true", help=_("display more detailed information about check results")) parser.add_argument("-o", "--output", action="store", nargs="+", default=["ansi", "html"], choices=["ansi", "json", "html"], help=_("format of check results")) parser.add_argument("--target", action="store", nargs="+", help=_("target specific checks to run")) parser.add_argument("--output-file", action="store", metavar="FILE", help=_("file to write output to")) parser.add_argument("-v", "--verbose", action="store_true", help=_("display the full tracebacks of any errors (also implies --log)")) parser.add_argument("-V", "--version", action="version", version=f"%(prog)s {__version__}") parser.add_argument("--logout", action=LogoutAction) args = parser.parse_args() global SLUG SLUG = args.slug if args.dev: args.offline = True args.verbose = True if args.offline: args.local = True if args.verbose: # Show lib50 commands being run in verbose mode logging.basicConfig(level=os.environ.get("CHECK50_LOGLEVEL", "INFO")) lib50.ProgressBar.DISABLED = True args.log = True # Filter out any duplicates from args.output seen_output = set() args.output = [output for output in args.output if not (output in seen_output or seen_output.add(output))] # Set excepthook excepthook.verbose = args.verbose excepthook.outputs = args.output excepthook.output_file = args.output_file if not args.local: commit_hash = lib50.push("check50", SLUG, internal.CONFIG_LOADER, data={"check50": True})[1] with lib50.ProgressBar("Waiting for results") if "ansi" in args.output else nullcontext(): tag_hash, results = await_results(commit_hash, SLUG) else: with lib50.ProgressBar("Checking") if not args.verbose and "ansi" in args.output else nullcontext(): # If developing, assume slug is a path to check_dir if args.dev: internal.check_dir = Path(SLUG).expanduser().resolve() if not internal.check_dir.is_dir(): raise internal.Error(_("{} is not a directory").format(internal.check_dir)) else: # Otherwise have lib50 create a local copy of slug try: internal.check_dir = lib50.local(SLUG, offline=args.offline) except lib50.ConnectionError: raise internal.Error(_("check50 could not retrieve checks from GitHub. Try running check50 again with --offline.").format(SLUG)) except lib50.InvalidSlugError: raise_invalid_slug(SLUG, offline=args.offline) # Load config config = internal.load_config(internal.check_dir) # Compile local checks if necessary if isinstance(config["checks"], dict): config["checks"] = internal.compile_checks(config["checks"], prompt=args.dev) install_translations(config["translations"]) if not args.offline: install_dependencies(config["dependencies"], verbose=args.verbose) checks_file = (internal.check_dir / config["checks"]).resolve() # Have lib50 decide which files to include included = lib50.files(config.get("files"))[0] # Only open devnull conditionally ctxmanager = open(os.devnull, "w") if not args.verbose else nullcontext() with ctxmanager as devnull: if args.verbose: stdout = sys.stdout stderr = sys.stderr else: stdout = stderr = devnull # Create a working_area (temp dir) with all included student files named - with lib50.working_area(included, name='-') as working_area, \ contextlib.redirect_stdout(stdout), \ contextlib.redirect_stderr(stderr): runner = CheckRunner(checks_file) # Run checks if args.target: check_results = runner.run(args.target, included, working_area) else: check_results = runner.run_all(included, working_area) results = { "slug": SLUG, "results": [attr.asdict(result) for result in check_results], "version": __version__ } # Render output file_manager = open(args.output_file, "w") if args.output_file else nullcontext(sys.stdout) with file_manager as output_file: for output in args.output: if output == "json": output_file.write(renderer.to_json(**results)) output_file.write("\n") elif output == "ansi": output_file.write(renderer.to_ansi(**results, log=args.log)) output_file.write("\n") elif output == "html": if os.environ.get("CS50_IDE_TYPE") and args.local: html = renderer.to_html(**results) subprocess.check_call(["c9", "exec", "renderresults", "check50", html]) else: if args.local: html = renderer.to_html(**results) with tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".html") as html_file: html_file.write(html) url = f"file://{html_file.name}" else: url = f"https://submit.cs50.io/check50/{tag_hash}" termcolor.cprint(_("To see the results in your browser go to {}").format(url), "white", attrs=["bold"])
def main(): parser = argparse.ArgumentParser( prog="check50", formatter_class=argparse.RawTextHelpFormatter ) parser.add_argument("slug", help=_( "prescribed identifier of work to check")) parser.add_argument( "-d", "--dev", action="store_true", help=_( "run check51 in development mode (implies --offline, and --log-level info).\n" "causes slug to be interpreted as a literal path to a checks package." ), ) parser.add_argument( "--offline", action="store_true", help=_( "run checks completely offline (implies --local, --no-download-checks and --no-install-dependencies)" ), ) parser.add_argument( "-l", "--local", action="store_true", help=_("run checks locally instead of uploading to cs50"), ) parser.add_argument( "-o", "--output", action="store", nargs="+", default=["ansi", "html"], choices=["ansi", "json", "html"], help=_("format of check results"), ) parser.add_argument( "--target", action="store", nargs="+", help=_("target specific checks to run") ) parser.add_argument( "--output-file", action="store", metavar="FILE", help=_("file to write output to"), ) parser.add_argument( "--log-level", action="store", choices=[level.name.lower() for level in LogLevel], type=str.lower, help=_( "warning: displays usage warnings." "\ninfo: adds all commands run, any locally installed dependencies and print messages." "\ndebug: adds the output of all commands run." ), ) parser.add_argument( "--ansi-log", action="store_true", help=_("display log in ansi output mode") ) parser.add_argument( "--no-download-checks", action="store_true", help=_( "do not download checks, but use previously downloaded checks instead (only works with --local)" ), ) parser.add_argument( "--no-install-dependencies", action="store_true", help=_("do not install dependencies (only works with --local)"), ) parser.add_argument( "-V", "--version", action="version", version=f"%(prog)s {__version__}" ) parser.add_argument("--logout", action=LogoutAction) args = parser.parse_args() internal.slug = args.slug # Validate arguments and apply defaults process_args(args) # Set excepthook _exceptions.ExceptHook.initialize(args.output, args.output_file) # Force --dev mode on! args.dev = True args.local = True args.log_level = "info" # """ # If remote, push files to GitHub and await results if not args.local: commit_hash = lib50.push( "check50", internal.slug, internal.CONFIG_LOADER, data={"check50": True} )[1] with lib50.ProgressBar( "Waiting for results" ) if "ansi" in args.output else nullcontext(): tag_hash, results = await_results(commit_hash, internal.slug) # Otherwise run checks locally else: with lib50.ProgressBar("Checking") if "ansi" in args.output else nullcontext(): # If developing, assume slug is a path to check_dir if args.dev: # Note: internal.check_DIR is the location of the slug internal.check_dir = Path(internal.slug).expanduser().resolve() if not internal.check_dir.is_dir(): raise _exceptions.Error( ("{} is not a directory").format(internal.check_dir) ) # Otherwise have lib50 create a local copy of slug else: try: internal.check_dir = lib50.local( internal.slug, offline=args.no_download_checks ) except lib50.ConnectionError: raise _exceptions.Error( _( "check51 could not retrieve checks from GitHub. Try running check51 again with --offline." ).format(internal.slug) ) except lib50.InvalidSlugError: raise_invalid_slug( internal.slug, offline=args.no_download_checks) # Load config config = internal.load_config(internal.check_dir) # Compile local checks if necessary if isinstance(config["checks"], dict): config["checks"] = internal.compile_checks( config["checks"], prompt=args.dev ) install_translations(config["translations"]) if not args.no_install_dependencies: install_dependencies(config["dependencies"]) checks_file = (internal.check_dir / config["checks"]).resolve() # Have lib50 decide which files to include included_files = lib50.files(config.get("files"))[0] # Create a working_area (temp dir) named - with all included student files with CheckRunner( checks_file, included_files ) as check_runner, contextlib.redirect_stdout( LoggerWriter(LOGGER, logging.INFO) ), contextlib.redirect_stderr( LoggerWriter(LOGGER, logging.INFO) ): check_results = check_runner.run(args.target) results = { "slug": internal.slug, "results": [attr.asdict(result) for result in check_results], "version": __version__, } LOGGER.debug(results) # Render output file_manager = ( open(args.output_file, "w") if args.output_file else nullcontext(sys.stdout) ) with file_manager as output_file: for output in args.output: if output == "json": output_file.write(renderer.to_json(**results)) output_file.write("\n") elif output == "ansi": output_file.write(renderer.to_ansi( **results, _log=args.ansi_log)) output_file.write("\n") elif output == "html": if os.environ.get("CS50_IDE_TYPE") and args.local: html = renderer.to_html(**results) subprocess.check_call( ["c9", "exec", "renderresults", "check50", html] ) else: if args.local: html = renderer.to_html( **results ) # The HTML text that needs to go into the file. outputDir = f"{internal.check_dir}/outputs" with open( f"{outputDir}/test{datetime.now().isoformat()}.html", "x+", ) as html_file: html_file.write(html) url = f"file://{html_file.name}" else: url = f"https://submit.cs50.io/check50/{tag_hash}" termcolor.cprint( _("To see the results in your browser go to {}").format(url), "white", attrs=["bold"], )
def main(): parser = argparse.ArgumentParser(prog="check50") parser.add_argument("slug", help=_("prescribed identifier of work to check")) parser.add_argument( "-d", "--dev", action="store_true", help= _("run check50 in development mode (implies --offline and --verbose info).\n" "causes SLUG to be interpreted as a literal path to a checks package" )) parser.add_argument( "--offline", action="store_true", help= _("run checks completely offline (implies --local, --no-download-checks and --no-install-dependencies)" )) parser.add_argument( "-l", "--local", action="store_true", help=_("run checks locally instead of uploading to cs50")) parser.add_argument( "--log", action="store_true", help=_("display more detailed information about check results")) parser.add_argument("-o", "--output", action="store", nargs="+", default=["ansi", "html"], choices=["ansi", "json", "html"], help=_("format of check results")) parser.add_argument("--target", action="store", nargs="+", help=_("target specific checks to run")) parser.add_argument("--output-file", action="store", metavar="FILE", help=_("file to write output to")) parser.add_argument( "-v", "--verbose", action="store", nargs="?", default="", const="info", choices=[ attr for attr in dir(logging) if attr.isupper() and isinstance(getattr(logging, attr), int) ], type=str.upper, help=_( "sets the verbosity level." ' "INFO" displays the full tracebacks of errors and shows all commands run.' ' "DEBUG" adds the output of all command run.')) parser.add_argument( "--no-download-checks", action="store_true", help= _("do not download checks, but use previously downloaded checks instead (only works with --local)" )) parser.add_argument( "--no-install-dependencies", action="store_true", help=_("do not install dependencies (only works with --local)")) parser.add_argument("-V", "--version", action="version", version=f"%(prog)s {__version__}") parser.add_argument("--logout", action=LogoutAction) args = parser.parse_args() global SLUG SLUG = args.slug # dev implies offline and verbose "info" if not overwritten if args.dev: args.offline = True if not args.verbose: args.verbose = "info" # offline implies local if args.offline: args.no_install_dependencies = True args.no_download_checks = True args.local = True # Setup logging for lib50 depending on verbosity level setup_logging(args.verbose) # Warning in case of running remotely with no_download_checks or no_install_dependencies set if not args.local: useless_args = [] if args.no_download_checks: useless_args.append("--no-downloads-checks") if args.no_install_dependencies: useless_args.append("--no-install-dependencies") if useless_args: termcolor.cprint(_( "Warning: you should always use --local when using: {}".format( ", ".join(useless_args))), "yellow", attrs=["bold"]) # Filter out any duplicates from args.output seen_output = set() args.output = [ output for output in args.output if not (output in seen_output or seen_output.add(output)) ] # Set excepthook excepthook.verbose = bool(args.verbose) excepthook.outputs = args.output excepthook.output_file = args.output_file # If remote, push files to GitHub and await results if not args.local: commit_hash = lib50.push("check50", SLUG, internal.CONFIG_LOADER, data={"check50": True})[1] with lib50.ProgressBar("Waiting for results" ) if "ansi" in args.output else nullcontext(): tag_hash, results = await_results(commit_hash, SLUG) # Otherwise run checks locally else: with lib50.ProgressBar( "Checking") if "ansi" in args.output else nullcontext(): # If developing, assume slug is a path to check_dir if args.dev: internal.check_dir = Path(SLUG).expanduser().resolve() if not internal.check_dir.is_dir(): raise internal.Error( _("{} is not a directory").format(internal.check_dir)) # Otherwise have lib50 create a local copy of slug else: try: internal.check_dir = lib50.local( SLUG, offline=args.no_download_checks) except lib50.ConnectionError: raise internal.Error( _("check50 could not retrieve checks from GitHub. Try running check50 again with --offline." ).format(SLUG)) except lib50.InvalidSlugError: raise_invalid_slug(SLUG, offline=args.no_download_checks) # Load config config = internal.load_config(internal.check_dir) # Compile local checks if necessary if isinstance(config["checks"], dict): config["checks"] = internal.compile_checks(config["checks"], prompt=args.dev) install_translations(config["translations"]) if not args.no_install_dependencies: install_dependencies(config["dependencies"], verbose=args.verbose) checks_file = (internal.check_dir / config["checks"]).resolve() # Have lib50 decide which files to include included = lib50.files(config.get("files"))[0] with open(os.devnull, "w") if args.verbose else nullcontext() as devnull: # Redirect stdout to devnull if some verbosity level is set if args.verbose: stdout = stderr = devnull else: stdout = sys.stdout stderr = sys.stderr # Create a working_area (temp dir) named - with all included student files with lib50.working_area(included, name='-') as working_area, \ contextlib.redirect_stdout(stdout), \ contextlib.redirect_stderr(stderr): check_results = CheckRunner(checks_file).run( included, working_area, args.target) results = { "slug": SLUG, "results": [attr.asdict(result) for result in check_results], "version": __version__ } # Render output file_manager = open(args.output_file, "w") if args.output_file else nullcontext(sys.stdout) with file_manager as output_file: for output in args.output: if output == "json": output_file.write(renderer.to_json(**results)) output_file.write("\n") elif output == "ansi": output_file.write(renderer.to_ansi(**results, log=args.log)) output_file.write("\n") elif output == "html": if os.environ.get("CS50_IDE_TYPE") and args.local: html = renderer.to_html(**results) subprocess.check_call( ["c9", "exec", "renderresults", "check50", html]) else: if args.local: html = renderer.to_html(**results) with tempfile.NamedTemporaryFile( mode="w", delete=False, suffix=".html") as html_file: html_file.write(html) url = f"file://{html_file.name}" else: url = f"https://submit.cs50.io/check50/{tag_hash}" termcolor.cprint( _("To see the results in your browser go to {}" ).format(url), "white", attrs=["bold"])
def main(): parser = ArgumentParser(prog="help50", description="A command-line tool that helps " "students understand error messages.") parser.add_argument("-s", "--slug", help="identifier indicating from where to download helpers", default="cs50/helpers/master") parser.add_argument("-d", "--dev", help="slug will be treated as a local path, useful for developing helpers (implies --verbose)", action="store_true") parser.add_argument("-v", "--verbose", help="display the full tracebacks of any errors", action="store_true") parser.add_argument("-V", "--version", action="version", version=f"%(prog)s {__version__}") parser.add_argument("command", nargs=REMAINDER, default=[], help="command to be run") args = parser.parse_args() if args.dev: args.verbose = True excepthook.verbose = args.verbose if args.command: command = " ".join(shlex.quote(word) for word in args.command) script = b"" # pty isn't supported on Windows if ON_WINDOWS: import subprocess proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) for b in iter(lambda: proc.stdout.read(1), b""): script += b sys.stdout.buffer.write(b) sys.stdout.flush() else: import pty def master_read(fd): nonlocal script data = os.read(fd, 1024) script += data return data old_env = os.environ.copy() # Hack to prevent some programs from wrapping their error messages os.environ.update({"COLUMNS": "5050"}) try: # Run the process in a pseudo-terminal e.g., to preserve colored output pty.spawn(["bash", "-lc", command], master_read) finally: os.environ.clear() os.environ.update(old_env) script = script.decode().replace("\r\n", "\n") else: raise Error("Careful, you forgot to tell me with which command you " "need help!") termcolor.cprint("\nAsking for help...\n", "yellow") try: helpers_dir = args.slug if args.dev else lib50.local(args.slug) except lib50.Error: raise Error("Failed to fetch helpers, please ensure that you are connected to the internet!") internal.load_helpers(helpers_dir) render_help(internal.get_help(script))