Beispiel #1
0
def uninstall_autocomplete() -> None:
    """
    Remove tab autocompletion from your shell profile.
    """
    path, text = _validate_shell()

    # Removes "{text}\n" from file; however, it leaves the preceding newline.
    # This is simply because the logic got complicated otherwise.

    omit_next_newline = False
    swap_fd, swap_path = tempfile.mkstemp(prefix="path_", text=True)
    try:
        with path.open() as profile_fd:
            for line in profile_fd:
                if line.strip() == text:
                    omit_next_newline = True
                elif omit_next_newline and line == "\n":
                    omit_next_newline = False
                else:
                    omit_next_newline = False
                    os.write(swap_fd, line.encode())
        os.fsync(swap_fd)
        shutil.copyfile(swap_path, path)
    finally:
        os.close(swap_fd)
        os.remove(swap_path)

    echo_success(f"Tab autocompletion for Bento has been removed from {path}.")
Beispiel #2
0
def uninstall_autorun(context: Context) -> None:
    """
    Configures Bento to NOT run automatically on commits.

    Autorun is only removed for the project from which this
    command is run.
    """
    import git  # import inside def for performance

    # Get hook path
    repo = bento.git.repo(context.base_path)
    if repo is None:
        raise NotAGitRepoException()

    hook_path = Path(git.index.fun.hook_path("pre-commit", repo.git_dir))

    if not _is_bento_precommit(hook_path):
        echo_warning(
            "Not uninstalling autorun: Bento is not configured for autorun on this project."
        )
        sys.exit(1)
    else:
        # Put back legacy hook if exits
        legacy_hook_path = Path(f"{hook_path}.pre-bento")
        if legacy_hook_path.exists():
            shutil.move(legacy_hook_path, hook_path)
        else:
            hook_path.unlink()

        echo_success("Uninstalled Bento autorun.")
        echo_next_step("To enable autorun", "bento enable autorun")
Beispiel #3
0
def tool(context: Context, tool: str) -> None:
    """
        Turn OFF a tool.

        Please see `bento disable --help` for more information.
    """
    update_tool_run(context, tool, False)
    echo_success(f"{tool} disabled")
Beispiel #4
0
    def setup(self) -> None:
        # import inside def for performance
        import docker

        client = docker.from_env()
        if not any(i for i in client.images.list() if self.DOCKER_IMAGE in i.tags):
            client.images.pull(self.DOCKER_IMAGE)
            echo_success(f"Retrieved {self.TOOL_ID} Container")
Beispiel #5
0
def tool(context: Context, tool: str) -> None:
    """
        Turn ON a tool.

        See `bento enable --help` for more detail.
    """
    update_tool_run(context, tool, True)
    echo_success(f"{tool} enabled")
Beispiel #6
0
def tool(context: Context, tool: str) -> None:
    """
    Turn OFF a tool.

    Tool-specific configurations are saved, and can be reenabled via `bento enable tool TOOL`.

    Please see `bento disable --help` for more information.
    """
    update_tool_run(context, tool, False)
    echo_success(f"{tool} disabled")
Beispiel #7
0
def check(context: Context, tool: str, check: str) -> None:
    """
    Turn OFF a check.

    Visit bento.dev/checks to learn about Bento's specialty checks.
    """
    def add(ignores: Set[str]) -> None:
        ignores.add(check)

    update_ignores(context, tool, add)
    echo_success(f"'{check}' disabled for '{tool}'")
Beispiel #8
0
def check(context: Context, tool: str, check: str) -> None:
    """
        Turn OFF a check.

        Please see `bento disable --help` for more information.
    """
    def add(ignores: Set[str]) -> None:
        ignores.add(check)

    update_ignores(context, tool, add)
    echo_success(f"'{check}' disabled for '{tool}'")
Beispiel #9
0
def _notify_install(context: Context, block: bool) -> None:
    if not context.is_init:
        block_prefix = "" if block else "non-"
        echo_success(f"Installed Bento autorun in {block_prefix}blocking mode.")
        echo_next_step("To uninstall Bento autorun", "bento disable autorun")

        desc_prefix, cmd_prefix = ("", "") if not block else ("non-", "no-")
        echo_next_step(
            f"To make autorun {desc_prefix}blocking",
            f"bento enable autorun --{cmd_prefix}block",
        )
Beispiel #10
0
def tool(context: Context, tool: str) -> None:
    """
    Turn ON a tool.

    If the tool was previously enabled, the tool's previous
    settings will be used. If no configuration exists, smart defaults will
    be applied.

    See `bento enable --help` for more details.
    """
    update_tool_run(context, tool, True)
    echo_success(f"{tool} enabled")
Beispiel #11
0
def check(context: Context, tool: str, check: str) -> None:
    """
    Turn ON a check.

    Visit checks.bento.dev to learn about Bento's specialty checks.
    """
    def remove(ignores: Set[str]) -> None:
        if check in ignores:
            ignores.remove(check)

    update_ignores(context, tool, remove)
    echo_success(f"'{check}' enabled for '{tool}'")
Beispiel #12
0
def check(context: Context, tool: str, check: str) -> None:
    """
        Turn ON a check.

        See `bento enable --help` for more details.
    """
    def remove(ignores: Set[str]) -> None:
        if check in ignores:
            ignores.remove(check)

    update_ignores(context, tool, remove)
    echo_success(f"'{check}' enabled for '{tool}'")
Beispiel #13
0
def install_autocomplete(quiet: bool = False) -> None:
    """
    Enable tab autocompletion in your shell profile.
    """
    path, text = _validate_shell()

    if not path.exists():
        path.touch(mode=0o644)

    if not file_has_text(path, text):
        append_text_to_file(path, text)

    if not quiet:
        echo_success(
            f"Tab autocompletion for Bento has been added to {path}. Please run `source {path}`."
        )
Beispiel #14
0
def uninstall_ci(context: Context) -> None:
    """
    Configures Bento to NOT run in CI.
    """
    repo = bento.git.repo(context.base_path)
    if repo is None:
        raise NotAGitRepoException()

    if not context.gh_actions_file_path.exists():
        echo_warning(
            "Not uninstalling CI config: Bento is not configured for CI on this project."
        )
        sys.exit(1)

    _delete_gh_actions_config(path=context.gh_actions_file_path,
                              root_path=context.base_path)

    echo_success("Uninstalled Bento from CI.")
    echo_next_step("To re-enable CI integration", "bento enable ci")
Beispiel #15
0
def check(
    context: Context,
    all_: bool = False,
    formatter: Tuple[str, ...] = (),
    pager: bool = True,
    tool: Optional[str] = None,
    staged_only: bool = False,  # Should not be used. Legacy support for old pre-commit hooks
    paths: Tuple[Path, ...] = (),
) -> None:
    """
    Checks for new findings.

    By default, only staged files are checked. New findings introduced by
    these staged changes AND that are not in the archive (`.bento/archive.json`)
    will be shown.

    Use `--all` to check all Git tracked files, not just those that are staged:

        $ bento check --all [PATHS]

    Optional PATHS can be specified to check specific directories or files.

    See `bento archive --help` to learn about suppressing findings.
    """

    # Fail out if not configured
    if not context.config_path.exists():
        raise NoConfigurationException()

    # Fail out if no .bentoignore
    if not context.ignore_file_path.exists():
        raise NoIgnoreFileException(context)

    # Default to no path filter
    if len(paths) < 1:
        path_list = [context.base_path]
    else:
        path_list = list(paths)

    # Handle specified tool that is not configured
    if tool and tool not in context.configured_tools:
        click.echo(
            f"{tool} has not been configured. Adding default configuration for tool to {bento.constants.CONFIG_FILE_NAME}"
        )
        update_tool_run(context, tool, False)
        # Set configured_tools to None so that future calls will
        # update and include newly added tool
        context._configured_tools = None

    # Handle specified formatters
    if formatter:
        context.config["formatter"] = [{f: {}} for f in formatter]

    if all_:
        click.echo(f"Running Bento checks on all tracked files...\n", err=True)
    else:
        click.echo(f"Running Bento checks on staged files...\n", err=True)

    tools: Iterable[Tool[Any]] = context.tools.values()
    if tool:
        tools = [context.configured_tools[tool]]

    baseline: Baseline = {}
    if context.baseline_file_path.exists():
        with context.baseline_file_path.open() as json_file:
            baseline = bento.result.json_to_violation_hashes(json_file)

    target_file_manager = TargetFileManager(
        context.base_path, path_list, not all_, context.ignore_file_path
    )

    all_results, elapsed = bento.orchestrator.orchestrate(
        baseline, target_file_manager, not all_, tools
    )

    fmts = context.formatters
    findings_to_log: List[Any] = []
    n_all = 0
    n_all_filtered = 0
    filtered_findings: Dict[str, List[Violation]] = {}
    for tool_id, findings in all_results:
        if isinstance(findings, Exception):
            logging.error(findings)
            echo_error(f"Error while running {tool_id}: {findings}")
            if isinstance(findings, BentoException):
                click.secho(findings.msg, err=True)
            else:
                if isinstance(findings, subprocess.CalledProcessError):
                    click.secho(findings.stderr, err=True)
                    click.secho(findings.stdout, err=True)
                if isinstance(findings, NodeError):
                    echo_warning(
                        f"Node.js not found or version is not compatible with ESLint v6."
                    )
                click.secho(
                    f"""-------------------------------------------------------------------------------------------------
    This may be due to a corrupted tool installation. You might be able to fix this issue by running:

    bento init --clean

    You can also view full details of this error in `{bento.constants.DEFAULT_LOG_PATH}`.
    -------------------------------------------------------------------------------------------------
    """,
                    err=True,
                )
            context.error_on_exit(ToolRunException())
        elif isinstance(findings, list) and findings:
            findings_to_log += bento.metrics.violations_to_metrics(
                tool_id,
                context.timestamp,
                findings,
                __get_ignores_for_tool(tool_id, context.config),
            )
            filtered = [f for f in findings if not f.filtered]
            filtered_findings[tool_id] = filtered

            n_all += len(findings)
            n_filtered = len(filtered)
            n_all_filtered += n_filtered
            logging.debug(f"{tool_id}: {n_filtered} findings passed filter")

    def post_metrics() -> None:
        bento.network.post_metrics(findings_to_log, is_finding=True)

    stats_thread = threading.Thread(name="stats", target=post_metrics)
    stats_thread.start()

    dumped = [f.dump(filtered_findings) for f in fmts]
    context.start_user_timer()
    bento.util.less(dumped, pager=pager, overrun_pages=OVERRUN_PAGES)
    context.stop_user_timer()

    finding_source_text = "in this project" if all_ else "due to staged changes"
    if n_all_filtered > 0:
        echo_warning(
            f"{n_all_filtered} finding(s) {finding_source_text} in {elapsed:.2f} s"
        )
        click.secho("\nPlease fix these issues, or:\n", err=True)
        echo_next_step("To archive findings as tech debt", f"bento archive")
        echo_next_step("To disable a specific check", f"bento disable check TOOL CHECK")
    else:
        echo_success(f"0 findings {finding_source_text} in {elapsed:.2f} s\n")

    n_archived = n_all - n_all_filtered
    if n_archived > 0:
        echo_next_step(
            f"Not showing {n_archived} archived finding(s). To view",
            "cat .bento/archive.json",
        )

    if not all_ and not context.autorun_is_blocking:
        return
    elif context.on_exit_exception:
        raise context.on_exit_exception
    elif n_all_filtered > 0:
        sys.exit(2)
Beispiel #16
0
def check(
    context: Context,
    formatter: Tuple[str, ...] = (),
    pager: bool = True,
    show_all: bool = False,
    staged_only: bool = False,
    tool: Optional[str] = None,
    paths: Optional[List[str]] = None,
) -> None:
    """
    Checks for new findings.

    Only findings not previously archived will be displayed (use --show-all
    to display archived findings).

    By default, 'bento check' will check the entire project. To run
    on one or more paths only, run:

      bento check path1 path2 ...
    """
    if tool and tool not in context.configured_tools:
        click.echo(
            f"{tool} has not been configured. Adding default configuration for tool to .bento.yml"
        )
        update_tool_run(context, tool, False)
        # Set configured_tools to None so that future calls will
        # update and include newly added tool
        context._configured_tools = None

    if not context.config_path.exists():
        echo_error("No Bento configuration found. Please run `bento init`.")
        sys.exit(3)

    if not show_all and context.baseline_file_path.exists():
        with context.baseline_file_path.open() as json_file:
            baseline = bento.result.yml_to_violation_hashes(json_file)
    else:
        baseline = {}

    config = context.config
    if formatter:
        config["formatter"] = [{f: {}} for f in formatter]
    fmts = context.formatters
    findings_to_log: List[Any] = []

    click.echo("Running Bento checks...\n", err=True)

    ctx = noop_context()
    if paths and len(paths) > 0:
        if staged_only:
            raise Exception(
                "--staged_only should not be used with explicit paths")
    elif staged_only:
        ctx = staged_files_only(
            os.path.join(os.path.expanduser("~"), ".cache", "bento",
                         "patches"))
        paths = get_staged_files()
    else:
        paths = None

    with ctx:
        before = time.time()
        runner = bento.tool_runner.Runner()
        tools: Iterable[Tool[Any]] = context.tools.values()

        if tool:
            tools = [context.configured_tools[tool]]

        all_results = runner.parallel_results(tools, baseline, paths)
        elapsed = time.time() - before

    # Progress bars terminate on whitespace
    echo_newline()

    is_error = False

    n_all = 0
    n_all_filtered = 0
    filtered_findings: Dict[str, List[Violation]] = {}
    for tool_id, findings in all_results:
        if isinstance(findings, Exception):
            logging.error(findings)
            echo_error(f"Error while running {tool_id}: {findings}")
            if isinstance(findings, subprocess.CalledProcessError):
                click.secho(findings.stderr, err=True)
                click.secho(findings.stdout, err=True)
            if isinstance(findings, NodeError):
                echo_warning(
                    f"Node.js not found or version is not compatible with ESLint v6."
                )

            click.secho(
                f"""-------------------------------------------------------------------------------------------------
This may be due to a corrupted tool installation. You might be able to fix this issue by running:

  bento init --clean

You can also view full details of this error in `{bento.constants.DEFAULT_LOG_PATH}`.
-------------------------------------------------------------------------------------------------
""",
                err=True,
            )
            is_error = True
        elif isinstance(findings, list) and findings:
            findings_to_log += bento.metrics.violations_to_metrics(
                tool_id,
                context.timestamp,
                findings,
                __get_ignores_for_tool(tool_id, config),
            )
            filtered = [f for f in findings if not f.filtered]
            filtered_findings[tool_id] = filtered

            n_all += len(findings)
            n_filtered = len(filtered)
            n_all_filtered += n_filtered
            logging.debug(f"{tool_id}: {n_filtered} findings passed filter")

    def post_metrics() -> None:
        bento.network.post_metrics(findings_to_log, is_finding=True)

    stats_thread = threading.Thread(name="stats", target=post_metrics)
    stats_thread.start()

    if n_all_filtered > 0:
        dumped = [f.dump(filtered_findings) for f in fmts]
        context.start_user_timer()
        bento.util.less(dumped, pager=pager, overrun_pages=OVERRUN_PAGES)
        context.stop_user_timer()

        echo_warning(f"{n_all_filtered} finding(s) in {elapsed:.2f} s\n")
        if not context.is_init:
            echo_next_step("To suppress all findings", "bento archive")
    else:
        echo_success(f"0 findings in {elapsed:.2f} s\n")

    n_archived = n_all - n_all_filtered
    if n_archived > 0 and not show_all:
        echo_next_step(
            f"Not showing {n_archived} archived finding(s). To view",
            f"bento check {SHOW_ALL}",
        )

    if is_error:
        sys.exit(3)
    elif n_all_filtered > 0:
        sys.exit(2)