コード例 #1
0
ファイル: lib50_tests.py プロジェクト: cs50/lib50
    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)
コード例 #2
0
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))
コード例 #3
0
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)
コード例 #4
0
ファイル: __main__.py プロジェクト: systempaxos/check50
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"])
コード例 #5
0
ファイル: __main__.py プロジェクト: bacarpenter/check51
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"],
                    )
コード例 #6
0
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"])
コード例 #7
0
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))