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}.")
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")
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")
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")
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")
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")
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}'")
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}'")
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", )
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")
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}'")
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}'")
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}`." )
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")
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)
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)