def _print_target_help(self, target_alias: str, show_advanced: bool) -> None: self._print_title(f"`{target_alias}` target") tinfo = self._all_help_info.name_to_target_type_info[target_alias] if tinfo.description: formatted_desc = "\n".join( hard_wrap(tinfo.description, width=self._width)) print(formatted_desc) print(f"\n\nActivated by {self.maybe_magenta(tinfo.provider)}") print("Valid fields:") for field in sorted(tinfo.fields, key=lambda x: (-x.required, x.alias)): print() print(self.maybe_magenta(field.alias)) indent = " " required_or_default = "required" if field.required else f"default: {field.default}" if field.provider not in ["", tinfo.provider]: print(self.maybe_cyan(f"{indent}from: {field.provider}")) print(self.maybe_cyan(f"{indent}type: {field.type_hint}")) print(self.maybe_cyan(f"{indent}{required_or_default}")) if field.description: formatted_desc = "\n".join( hard_wrap(field.description, indent=len(indent), width=self._width)) print("\n" + formatted_desc) print()
def add_option(ohis, *, category=None): lines.append("") goal_or_subsystem = "goal" if oshi.is_goal else "subsystem" display_scope = f"`{oshi.scope}` {goal_or_subsystem}" if oshi.scope else "Global" if category: title = f"{display_scope} {category} options" lines.append(self.maybe_green(f"{title}\n{'-' * len(title)}")) else: # The basic options section gets the description and options scope info. # No need to repeat those in the advanced section. title = f"{display_scope} options" lines.append( self.maybe_green(f"{title}\n{'-' * len(title)}\n")) lines.extend(hard_wrap(oshi.description, width=self._width)) lines.append(" ") lines.append( f"Activated by {self.maybe_magenta(oshi.provider)}") config_section = f"[{oshi.scope or 'GLOBAL'}]" lines.append( f"Config section: {self.maybe_magenta(config_section)}") lines.append(" ") if not ohis: lines.append("None available.") return for ohi in ohis: lines.extend([*self.format_option(ohi), ""])
def _print_api_type_help(self, name: str, show_advanced: bool) -> None: self._print_title(f"`{name}` api type") type_info = self._all_help_info.name_to_api_type_info[name] print("\n".join( hard_wrap(type_info.documentation or "Undocumented.", width=self._width))) print() self._print_table({ "activated by": type_info.provider, "union type": type_info.union_type, "union members": "\n".join(type_info.union_members) if type_info.is_union else None, "dependencies": "\n".join(type_info.dependencies) if show_advanced else None, "dependees": "\n".join(type_info.dependees) if show_advanced else None, f"returned by {pluralize(len(type_info.returned_by_rules), 'rule')}": "\n".join(type_info.returned_by_rules) if show_advanced else None, f"consumed by {pluralize(len(type_info.consumed_by_rules), 'rule')}": "\n".join(type_info.consumed_by_rules) if show_advanced else None, f"used in {pluralize(len(type_info.used_in_rules), 'rule')}": "\n".join(type_info.used_in_rules) if show_advanced else None, }) print() if not show_advanced: print( self.maybe_green( f"Include API types and rules dependency information by running " f"`{bin_name()} help-advanced {name}`.\n"))
def _print_target_help(self, target_alias: str) -> None: self._print_title(target_alias) tinfo = self._all_help_info.name_to_target_type_info[target_alias] if tinfo.description: formatted_desc = "\n".join(hard_wrap(tinfo.description)) print(formatted_desc) print("\n\nValid fields:") for field in sorted(tinfo.fields, key=lambda x: x.alias): print() print(self.maybe_magenta(field.alias)) indent = " " required_or_default = "required" if field.required else f"default: {field.default}" print(self.maybe_cyan(f"{indent}type: {field.type_hint}")) print(self.maybe_cyan(f"{indent}{required_or_default}")) if field.description: formatted_desc = "\n".join( hard_wrap(field.description, indent=len(indent))) print(formatted_desc) print()
def test_hard_wrap() -> None: assert hard_wrap("Hello world!", width=6) == ["Hello", "world!"] # Indents play into the width. assert hard_wrap("Hello world!", width=6, indent=2) == [" Hell", " o wo", " rld!"] assert hard_wrap("Hello world!", width=8, indent=2) == [" Hello", " world!"] # Preserve prior newlines. assert hard_wrap("- 1\n- 2\n\n") == ["- 1", "- 2", ""] assert hard_wrap("Hello world!\n\n- 1 some text\n- 2\n\nHola mundo!", width=6) == [ "Hello", "world!", "", "- 1", "some", "text", "- 2", "", "Hola", "mundo!", ]
def _print_table(self, table: Dict[str, Optional[str]], indent: int = 0) -> None: longest_key = max( len(key) for key, value in table.items() if value is not None) for key, value in table.items(): if value is None: continue print( self.maybe_cyan(f"{key:{longest_key}}:"), self.maybe_magenta(f"\n{' ':{longest_key+2}}".join( hard_wrap(value, width=self._width - longest_key - 2))), )
def _print_api_type_help(self, output_type: str, show_advanced: bool) -> None: self._print_title(f"`{output_type}` API type") rule_infos = self._all_help_info.rule_output_type_to_rule_infos[ output_type] if rule_infos[0].output_desc: print("\n".join( hard_wrap(rule_infos[0].output_desc, width=self._width))) print() print(f"Returned by {pluralize(len(rule_infos), 'rule')}:") for rule_info in rule_infos: print() print(self.maybe_magenta(rule_info.name)) indent = " " print(self.maybe_cyan(f"{indent}activated by"), rule_info.provider) if rule_info.input_types: print( self.maybe_cyan( f"{indent}{pluralize(len(rule_info.input_types), 'input')}:" ), ", ".join(rule_info.input_types), ) else: print(self.maybe_cyan(f"{indent}no inputs")) if show_advanced and rule_info.input_gets: print(f"\n{indent}".join( hard_wrap( self.maybe_cyan( f"{pluralize(len(rule_info.input_gets), 'get')}: ") + ", ".join(rule_info.input_gets), indent=4, width=self._width - 4, ))) if rule_info.description: print(f"{indent}{rule_info.description}") if rule_info.help: print("\n" + "\n".join( hard_wrap(rule_info.help, indent=4, width=self._width))) print()
def _print_rule_help(self, rule_name: str, show_advanced: bool) -> None: rule = self._all_help_info.name_to_rule_info[rule_name] title = f"`{rule_name}` rule" self._print_title(title) if rule.description: print(rule.description + "\n") print("\n".join( hard_wrap(rule.documentation or "Undocumented.", width=self._width))) print() self._print_table({ "activated by": rule.provider, "returns": rule.output_type, f"takes {pluralize(len(rule.input_types), 'input')}": ", ".join(rule.input_types), f"awaits {pluralize(len(rule.input_gets), 'get')}": "\n".join(rule.input_gets) if show_advanced else None, }) print()
def _print_all_api_types(self) -> None: self._print_title("Plugin API Types") api_type_descriptions: Dict[str, Tuple[str, str]] = {} indent_api_summary = 0 for api_info in self._all_help_info.name_to_api_type_info.values(): name = api_info.name if name.startswith("_"): continue if api_info.is_union: name += " <union>" summary = (api_info.documentation or "").split("\n", 1)[0] api_type_descriptions[name] = (api_info.module, summary) indent_api_summary = max(indent_api_summary, len(name) + 2, len(api_info.module) + 2) for name, (module, summary) in api_type_descriptions.items(): name = self.maybe_cyan(name.ljust(indent_api_summary)) description_lines = hard_wrap(summary or " ", indent=indent_api_summary, width=self._width) # Juggle the description lines, to inject the api type module on the second line flushed # left just below the type name (potentially sharing the line with the second line of # the description that will be aligned to the right). if len(description_lines) > 1: # Place in front of the description line. description_lines[ 1] = f"{module:{indent_api_summary}}{description_lines[1][indent_api_summary:]}" else: # There is no second description line. description_lines.append(module) # All description lines are indented, but the first line should be indented by the api # type name, so we strip that. description_lines[0] = description_lines[0][indent_api_summary:] description = "\n".join(description_lines) print(f"{name}{description}\n") api_help_cmd = f"{bin_name()} help [api_type/rule_name]" print( f"Use `{self.maybe_green(api_help_cmd)}` to get help for a specific API type or rule.\n" )
def format_option(self, ohi: OptionHelpInfo) -> List[str]: """Format the help output for a single option. :param ohi: Extracted information for option to print :return: Formatted help text for this option """ def maybe_parens(s: Optional[str]) -> str: return f" ({s})" if s else "" def format_value(ranked_val: RankedValue, prefix: str, left_padding: str) -> List[str]: if isinstance(ranked_val.value, (list, dict)): is_enum_list = (isinstance(ranked_val.value, list) and len(ranked_val.value) > 0 and isinstance(ranked_val.value[0], Enum)) normalized_val = ([ enum_elmt.value for enum_elmt in ranked_val.value ] if is_enum_list else ranked_val.value) val_lines = json.dumps(normalized_val, sort_keys=True, indent=4).split("\n") else: val_lines = [to_help_str(ranked_val.value)] val_lines[0] = f"{prefix}{val_lines[0]}" val_lines[ -1] = f"{val_lines[-1]}{maybe_parens(ranked_val.details)}" val_lines = [ self.maybe_cyan(f"{left_padding}{line}") for line in val_lines ] return val_lines indent = " " arg_lines = [ f" {self.maybe_magenta(args)}" for args in ohi.display_args ] arg_lines.append(self.maybe_magenta(f" {ohi.env_var}")) arg_lines.append(self.maybe_magenta(f" {ohi.config_key}")) choices = "" if ohi.choices is None else f"one of: [{', '.join(ohi.choices)}]" choices_lines = [ f"{indent}{' ' if i != 0 else ''}{self.maybe_cyan(s)}" for i, s in enumerate(textwrap.wrap(f"{choices}", self._width)) ] default_lines = format_value(RankedValue(Rank.HARDCODED, ohi.default), "default: ", indent) if not ohi.value_history: # Should never happen, but this keeps mypy happy. raise ValueError("No value history - options not parsed.") final_val = ohi.value_history.final_value curr_value_lines = format_value(final_val, "current value: ", indent) interesting_ranked_values = [ rv for rv in reversed(ohi.value_history.ranked_values) if rv.rank not in (Rank.NONE, Rank.HARDCODED, final_val.rank) ] value_derivation_lines = [ line for rv in interesting_ranked_values for line in format_value(rv, "overrode: ", f"{indent} ") ] description_lines = hard_wrap(ohi.help, indent=len(indent), width=self._width) lines = [ *arg_lines, *choices_lines, *default_lines, *curr_value_lines, *value_derivation_lines, *description_lines, ] if ohi.deprecated_message: maybe_colorize = self.maybe_red if ohi.deprecation_active else self.maybe_yellow lines.append(maybe_colorize(f"{indent}{ohi.deprecated_message}")) if ohi.removal_hint: lines.append(maybe_colorize(f"{indent}{ohi.removal_hint}")) return lines
def wrap(s: str) -> List[str]: return hard_wrap(s, indent=len(indent), width=self._width)