%PDF- %PDF-
Direktori : /lib/python3/dist-packages/CommandNotFound/ |
Current File : //lib/python3/dist-packages/CommandNotFound/CommandNotFound.py |
# (c) Zygmunt Krynicki 2005, 2006, 2007, 2008 # Licensed under GPL, see COPYING for the whole text from __future__ import ( print_function, absolute_import, ) import gettext import grp import json import logging import os import os.path import posix import sys import subprocess from CommandNotFound.db.db import SqliteDatabase if sys.version >= "3": _gettext_method = "gettext" else: _gettext_method = "ugettext" _ = getattr(gettext.translation("command-not-found", fallback=True), _gettext_method) def similar_words(word): """ return a set with spelling1 distance alternative spellings based on http://norvig.com/spell-correct.html """ alphabet = 'abcdefghijklmnopqrstuvwxyz-_0123456789' s = [(word[:i], word[i:]) for i in range(len(word) + 1)] deletes = [a + b[1:] for a, b in s if b] transposes = [a + b[1] + b[0] + b[2:] for a, b in s if len(b) > 1] replaces = [a + c + b[1:] for a, b in s for c in alphabet if b] inserts = [a + c + b for a, b in s for c in alphabet] return set(deletes + transposes + replaces + inserts) def user_can_sudo(): try: groups = posix.getgroups() return (grp.getgrnam("sudo")[2] in groups or grp.getgrnam("admin")[2] in groups) except KeyError: return False # the new style DB - if that exists we skip the legacy DB dbpath = "/var/lib/command-not-found/commands.db" class CommandNotFound(object): programs_dir = "programs.d" max_len = 256 prefixes = ( "/snap/bin", "/bin", "/usr/bin", "/usr/local/bin", "/sbin", "/usr/sbin", "/usr/local/sbin", "/usr/games") snap_cmd = "/usr/bin/snap" output_fd = sys.stderr def __init__(self, data_dir="/usr/share/command-not-found"): self.sources_list = self._getSourcesList() # a new style DB means we can skip loading the old legacy static DB if os.path.exists(dbpath) and os.access(dbpath, os.R_OK): self.db = SqliteDatabase(dbpath) else: raise FileNotFoundError("Cannot find database") self.user_can_sudo = user_can_sudo() self.euid = posix.geteuid() def spelling_suggestions(self, word, min_len=3): """ try to correct the spelling """ possible_alternatives = [] if not (min_len <= len(word) <= self.max_len): return possible_alternatives for w in similar_words(word): packages = self.get_packages(w) for (package, ver, comp) in packages: possible_alternatives.append((w, package, comp, ver)) return possible_alternatives def get_packages(self, command): return self.db.lookup(command) def get_snaps(self, command): exact_result = [] mispell_result = [] if not os.path.exists(self.snap_cmd): logging.debug("%s not exists" % self.snap_cmd) return [], [] try: with open(os.devnull) as devnull: output = subprocess.check_output( [self.snap_cmd, "advise-snap", "--format=json", "--command", command], stderr=devnull, universal_newlines=True) except subprocess.CalledProcessError as e: logging.debug("calling snap advice-snap returned an error: %s" % e) return [], [] logging.debug("got %s from snap advise-snap" % output) try: snaps = json.loads(output) except json.JSONDecodeError as e: logging.debug("cannot decoding json: %s" % e) return [], [] for snap in snaps: if snap["Command"] == command: exact_result.append((snap["Snap"], snap["Command"], snap.get("Version"))) else: mispell_result.append((snap["Command"], snap["Snap"], snap.get("Version"))) return exact_result, mispell_result def getBlacklist(self): try: with open(os.sep.join((os.getenv("HOME", "/root"), ".command-not-found.blacklist"))) as blacklist: return [line.strip() for line in blacklist if line.strip() != ""] except IOError: return [] def _getSourcesList(self): try: import apt_pkg from aptsources.sourceslist import SourcesList apt_pkg.init() except (SystemError, ImportError): return [] sources_list = set([]) # The matcher parses info files from # /usr/share/python-apt/templates/ # But we don't use the calculated data, skip it try: sources = SourcesList(withMatcher=False, deb822=True) except TypeError: sources = SourcesList(withMatcher=False) for source in sources: if not source.disabled and not source.invalid: for component in source.comps: sources_list.add(component) return sources_list def install_prompt(self, package_name): if not "COMMAND_NOT_FOUND_INSTALL_PROMPT" in os.environ: return if package_name: prompt = _("Do you want to install it? (N/y)") if sys.version >= '3': answer = input(prompt) raw_input = lambda x: x # pyflakes else: answer = raw_input(prompt) if sys.stdin.encoding and isinstance(answer, str): # Decode the answer so that we get an unicode value answer = answer.decode(sys.stdin.encoding) if answer.lower() == _("y"): if self.euid == 0: command_prefix = "" else: command_prefix = "sudo " install_command = "%sapt install %s" % (command_prefix, package_name) print("%s" % install_command, file=sys.stdout) subprocess.call(install_command.split(), shell=False) def print_spelling_suggestions(self, word, mispell_packages, mispell_snaps, max_alt=15): """ print spelling suggestions for packages and snaps """ if len(mispell_packages)+len(mispell_snaps) > max_alt: print(_("Command '%s' not found, but there are %s similar ones.") % (word, len(mispell_packages)), file=self.output_fd) self.output_fd.flush() return elif len(mispell_packages)+len(mispell_snaps) > 0: print(_("Command '%s' not found, did you mean:") % word, file=self.output_fd) for (command, snap, ver) in mispell_snaps: if ver: ver = " (%s)" % ver else: ver = "" print(_(" command '%s' from snap %s%s") % (command, snap, ver), file=self.output_fd) for (command, package, comp, ver) in mispell_packages: if ver: ver = " (%s)" % ver else: ver = "" print(_(" command '%s' from deb %s%s") % (command, package, ver), file=self.output_fd) if len(mispell_snaps) > 0: print(_("See 'snap info <snapname>' for additional versions."), file=self.output_fd) elif len(mispell_packages) > 0: if self.user_can_sudo: print(_("Try: %s <deb name>") % "sudo apt install", file=self.output_fd) else: print(_("Try: %s <deb name>") % "apt install", file=self.output_fd) self.output_fd.flush() def _print_exact_header(self, command): print(_("Command '%(command)s' not found, but can be installed with:") % { 'command': command}, file=self.output_fd) def advice_single_snap_package(self, command, packages, snaps): self._print_exact_header(command) snap = snaps[0] if self.euid == 0: print("snap install %s" % snap[0], file=self.output_fd) elif self.user_can_sudo: print("sudo snap install %s" % snap[0], file=self.output_fd) else: print("snap install %s" % snap[0], file=self.output_fd) print(_("Please ask your administrator.")) self.output_fd.flush() def advice_single_deb_package(self, command, packages, snaps): self._print_exact_header(command) if self.euid == 0: print("apt install %s" % packages[0][0], file=self.output_fd) self.install_prompt(packages[0][0]) elif self.user_can_sudo: print("sudo apt install %s" % packages[0][0], file=self.output_fd) self.install_prompt(packages[0][0]) else: print("apt install %s" % packages[0][0], file=self.output_fd) print(_("Please ask your administrator.")) if not packages[0][2] in self.sources_list: print(_("You will have to enable the component called '%s'") % packages[0][2], file=self.output_fd) self.output_fd.flush() def sudo(self): if self.euid != 0 and self.user_can_sudo: return "sudo " return "" def advice_multi_deb_package(self, command, packages, snaps): self._print_exact_header(command) pad = max([len(s[0]) for s in snaps+packages]) for i, package in enumerate(packages): ver = "" if package[1]: if i == 0 and len(package) > 1: ver = " # version %s, or" % (package[1]) else: ver = " # version %s" % (package[1]) if package[2] in self.sources_list: print("%sapt install %-*s%s" % (self.sudo(), pad, package[0], ver), file=self.output_fd) else: print("%sapt install %-*s%s" % (self.sudo(), pad, package[0], ver) + " (" + _("You will have to enable component called '%s'") % package[2] + ")", file=self.output_fd) if self.euid != 0 and not self.user_can_sudo: print(_("Ask your administrator to install one of them."), file=self.output_fd) self.output_fd.flush() def advice_multi_snap_packages(self, command, packages, snaps): self._print_exact_header(command) pad = max([len(s[0]) for s in snaps+packages]) for i, snap in enumerate(snaps): ver = "" if snap[2]: if i == 0 and len(snaps) > 0: ver = " # version %s, or" % snap[2] else: ver = " # version %s" % snap[2] print("%ssnap install %-*s%s" % (self.sudo(), pad, snap[0], ver), file=self.output_fd) print(_("See 'snap info <snapname>' for additional versions."), file=self.output_fd) self.output_fd.flush() def advice_multi_mixed_packages(self, command, packages, snaps): self._print_exact_header(command) pad = max([len(s[0]) for s in snaps+packages]) for i, snap in enumerate(snaps): ver="" if snap[2]: if i == 0: ver = " # version %s, or" % snap[2] else: ver = " # version %s" % snap[2] print("%ssnap install %-*s%s" % (self.sudo(), pad, snap[0], ver), file=self.output_fd) for package in packages: ver="" if package[1]: ver = " # version %s" % package[1] print("%sapt install %-*s%s" % (self.sudo(), pad, package[0], ver), file=self.output_fd) if len(snaps) == 1: print(_("See 'snap info %s' for additional versions.") % snaps[0][0], file=self.output_fd) else: print(_("See 'snap info <snapname>' for additional versions."), file=self.output_fd) self.output_fd.flush() def advise(self, command, ignore_installed=False): " give advice where to find the given command to stderr " def _in_prefix(prefix, command): " helper that returns if a command is found in the given prefix " return (os.path.exists(os.path.join(prefix, command)) and not os.path.isdir(os.path.join(prefix, command))) if len(command) > self.max_len: return False if command.startswith("/"): if os.path.exists(command): prefixes = [os.path.dirname(command)] else: prefixes = [] else: prefixes = [prefix for prefix in self.prefixes if _in_prefix(prefix, command)] # check if we have it in a common prefix that may not be in the PATH if prefixes and not ignore_installed: if len(prefixes) == 1: print(_("Command '%(command)s' is available in '%(place)s'") % {"command": command, "place": os.path.join(prefixes[0], command)}, file=self.output_fd) else: print(_("Command '%(command)s' is available in the following places") % {"command": command}, file=self.output_fd) for prefix in prefixes: print(" * %s" % os.path.join(prefix, command), file=self.output_fd) missing = list(set(prefixes) - set(os.getenv("PATH", "").split(":"))) if len(missing) > 0: print(_("The command could not be located because '%s' is not included in the PATH environment variable.") % ":".join(missing), file=self.output_fd) if "sbin" in ":".join(missing): print(_("This is most likely caused by the lack of administrative privileges associated with your user account."), file=self.output_fd) return False # do not give advice if we are in a situation where apt # or aptitude are not available (LP: #394843) if not (os.path.exists("/usr/bin/apt") or os.path.exists("/usr/bin/aptitude")): return False if command in self.getBlacklist(): return False # python is special, on 20.04 and newer we should encourage # people to use python3 only, and command-not-found depends on # python3, so must be available if command == "python": print(_("Command '%s' not found, did you mean:") % command, file=self.output_fd) print(_(" command '%s' from deb %s%s") % ("python3", "python3", ""), file=self.output_fd) print(_(" command '%s' from deb %s%s") % ("python", "python-is-python3", ""), file=self.output_fd) return True packages = self.get_packages(command) snaps, mispell_snaps = self.get_snaps(command) logging.debug("got debs: %s snaps: %s" % (packages, snaps)) if len(packages) == 0 and len(snaps) == 0: mispell_packages = self.spelling_suggestions(command) if len(mispell_packages) > 0 or len(mispell_snaps) > 0: self.print_spelling_suggestions(command, mispell_packages, mispell_snaps) elif len(packages) == 0 and len(snaps) == 1: self.advice_single_snap_package(command, packages, snaps) elif len(snaps) > 0 and len(packages) == 0: self.advice_multi_snap_packages(command, packages, snaps) elif len(packages) == 1 and len(snaps) == 0: self.advice_single_deb_package(command, packages, snaps) elif len(packages) > 1 and len(snaps) == 0: self.advice_multi_deb_package(command, packages, snaps) elif len(packages) > 0 and len(snaps) > 0: self.advice_multi_mixed_packages(command, packages, snaps) return (len(packages) > 0 or len(snaps) > 0 or len(mispell_snaps) > 0 or len(mispell_packages) > 0)