def format_source(source): # type: (Source) -> t.Text module, fname, lnum = source if config.clickable_filenames: # This can also be written as file://{hostname}/{fname}. # But specifying the hostname is not terribly useful, and it breaks # some things (e.g. curl, though curling a local file is silly anyway.) fname = color.linkify(fname, "file://" + fname) if lnum is not None: return "{}: {}:{}".format(color.module(module), fname, lnum) else: return "{}: {}".format(color.module(module), fname)
def format_docs(docs, ignore_modules=()): # type: (t.Iterable[t.Tuple[str, t.Text]], t.Container[str]) -> t.Iterable[t.Text] docs = list(docs) for module, doc in docs: if module in ignore_modules: continue doc = color.highlight(doc, "rst") if len(docs) == 1: yield doc elif "\n" in doc: yield "{}:".format(color.module(module)) yield doc else: yield "{}: {}".format(color.module(module), doc)
def _color_state(module): # type: (odoo.models.IrModuleModule) -> t.Text if module.state == "installed": return color.module(module.name) elif module.state in ("uninstalled", "uninstallable"): return color.red.bold(module.name) else: return color.yellow.bold(module.name)
def gitsource(thing): # type: (sources.Sourceable) -> None for source in sources.find_source(thing): try: fmt = format_source(source) except GitSourceError as exc: fmt = "{}: {}".format(color.module(source.module), color.missing(str(exc))) print(fmt)
def format_source(source): # type: (sources.Source) -> t.Text module, fname, lnum = source fname = to_url(fname) if lnum is not None: fname += "#L{}".format(lnum) if config.clickable_filenames: fname = color.linkify(fname, fname) return "{}: {}".format(color.module(module), fname)
def methods_(self): # type: () -> None self._ensure_real() for cls in type(self._real).__bases__: meths = [(name, attr) for name, attr in sorted(vars(cls).items()) if util.loosely_callable(attr) if name != "pool"] if meths: print() print(color.module(util.module(cls))) for name, meth in meths: print( color.method(name) + methods._func_signature(util.unpack_function(meth)))
def model_repr(obj): # type: (t.Union[ModelProxy, BaseModel]) -> t.Text """Summarize a model's fields.""" if isinstance(obj, ModelProxy) and obj._real is None: return repr(obj) obj = util.unwrap(obj) field_names = [] delegated = [] for field in sorted(obj._fields): if field in FIELD_BLACKLIST: continue if getattr(obj._fields[field], "related", False): delegated.append(obj._fields[field]) continue field_names.append(field) max_len = max(len(f) for f in field_names) if field_names else 0 parts = [] original_module = obj._module for parent in type(obj).__bases__: if getattr(parent, "_name", None) == obj._name: original_module = getattr(parent, "_module", original_module) parts.append(color.header(obj._name)) if obj._transient: parts[-1] += " (transient)" if getattr(obj, "_abstract", False): parts[-1] += " (abstract)" elif not obj._auto: parts[-1] += " (no automatic table)" if getattr(obj, "_description", False) and obj._description != obj._name: parts.append(color.display_name(obj._description)) if getattr(obj, "_inherits", False): for model_name, field_name in obj._inherits.items(): parts.append("Inherits from {} through {}".format( color.model(model_name), color.field(field_name))) inherits = _find_inheritance(obj) if inherits: # Giving this a very similar message to the one for _inherits feels dirty # But then _inherit is already very similar to _inherits so maybe it's ok parts.append("Inherits from {}".format(", ".join( color.model(inherit) for inherit in sorted(inherits)))) docs = list( sources.find_docs((util.module(cls), cls) for cls in type(obj).__bases__ if getattr(cls, "_name", obj._name) == obj._name)) parts.extend(sources.format_docs(docs)) src = sources.find_source(obj) by_module = collections.defaultdict(list) for field in field_names: f_obj = obj._fields[field] rep = format_single_field(f_obj, max_len=max_len) f_module = sources.find_field_module(f_obj) or original_module by_module[f_module].append(rep) ordered_modules = [original_module] for src_item in reversed(src): if src_item.module not in ordered_modules: ordered_modules.append(src_item.module) for module in by_module: if module not in ordered_modules: ordered_modules.append(module) for module in ordered_modules: if module not in by_module: continue parts.append("") parts.append("{}:".format(color.module(module))) parts.extend(by_module[module]) if delegated: buckets = collections.defaultdict( list) # type: t.DefaultDict[t.Tuple[t.Text, ...], t.List[t.Text]] for f_obj in delegated: assert f_obj.related buckets[tuple(f_obj.related[:-1])].append( color.field(f_obj.name) if f_obj.related[-1] == f_obj.name else "{} (.{})".format(color.field(f_obj.name), f_obj.related[-1])) parts.append("") for related_field, field_names in buckets.items(): # TODO: figure out name of model of real field parts.append("Delegated to {}: {}".format( color.yellow.bold(".".join(related_field)), ", ".join(field_names))) parts.append("") parts.extend(sources.format_sources(src)) return "\n".join(parts)
def addon_repr(addon): # type: (Addon) -> t.Text # TODO: A lot of the most interesting information is at the top so you have # to scroll up # Ideas: # - Put it at the bottom instead # - Don't show the README by default try: addon.manifest except RuntimeError: return repr(addon) defined_models = (addon._env["ir.model"].browse( addon._env["ir.model.data"].search([ ("model", "=", "ir.model"), ("module", "=", addon._module) ]).mapped("res_id")).mapped("model")) state = addon.record.state if (state == "installed" and addon.record.installed_version != addon.manifest.version): state += " (out of date)" if state == "installed": state = color.green.bold(state.capitalize()) elif not state: state = color.yellow.bold("???") elif state in ("uninstallable", "uninstalled"): state = color.red.bold(state.capitalize()) else: state = color.yellow.bold(state.capitalize()) description = addon.manifest.description if isinstance(addon.manifest.author, Text): author = addon.manifest.author else: author = ", ".join(addon.manifest.author) parts = [] parts.append("{} {} by {}".format(color.module(addon._module), addon.manifest.version, author)) parts.append(util.link_for_record(addon.record)) parts.append(addon.path) parts.append(state) parts.append(color.display_name(addon.manifest.name)) parts.append(addon.manifest.summary) def format_depends(pretext, modules): # type: (t.Text, odoo.models.IrModuleModule) -> None if modules: names = map(_color_state, sorted(modules, key=lambda mod: mod.name)) parts.append("{}: {}".format(pretext, ", ".join(names))) # TODO: Indirect dependencies are a bit noisy, when/how do we show them? direct, _indirect = addon._get_depends() format_depends("Depends", direct) # format_depends("Indirectly depends", indirect) r_direct, _r_indirect = addon._get_rdepends() format_depends("Dependents", r_direct) # format_depends("Indirect dependents", r_indirect) if defined_models: parts.append("Defines: {}".format(", ".join( map(color.model, defined_models)))) if description: parts.append("") # rst2ansi might be better here # (https://pypi.org/project/rst2ansi/) parts.append(color.highlight(description, "rst")) return "\n".join(parts)