Merge lp:~mvo/command-not-found/snap-support into lp:command-not-found

Proposed by Michael Vogt
Status: Merged
Merge reported by: Brian Murray
Merged at revision: not available
Proposed branch: lp:~mvo/command-not-found/snap-support
Merge into: lp:command-not-found
Diff against target: 199 lines (+62/-39)
3 files modified
CommandNotFound/CommandNotFound.py (+59/-38)
command-not-found (+1/-1)
debian/dirs (+2/-0)
To merge this branch: bzr merge lp:~mvo/command-not-found/snap-support
Reviewer Review Type Date Requested Status
Ubuntu Core Development Team Pending
Review via email: mp+335516@code.launchpad.net

Description of the change

This is a first preview for snap support in command-not-found. The idea is that snapd will populate /usr/share/command-not-found/snaps.d/all-main.db for command-not-found to consume.

To post a comment you must log in.
Revision history for this message
Brian Murray (brian-murray) wrote :

I believe this landed in Bionic.

command-not-found (0.3ubuntu18.04.0~pre3) bionic; urgency=medium

  * CommandNotFound/CommandNotFound.py:
    - limit input to 256 chars to avoid DoS (LP: #1605732)
    - add support for suggesting commands snap from snaps
      (needs snapd 2.31+ to work)
    - add "snapd" to suggests

 -- Michael Vogt <email address hidden> Thu, 15 Feb 2018 09:15:40 +0100

Revision history for this message
Brian Murray (brian-murray) wrote :

I'm setting this to merged to take it off the sponsorship list. Feel free to flip it back if I'm wrong.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'CommandNotFound/CommandNotFound.py'
--- CommandNotFound/CommandNotFound.py 2017-12-21 10:34:37 +0000
+++ CommandNotFound/CommandNotFound.py 2017-12-21 11:57:34 +0000
@@ -41,45 +41,39 @@
41 key = key.encode('utf-8', 'xmlcharrefreplace')41 key = key.encode('utf-8', 'xmlcharrefreplace')
42 if self.db and key in self.db:42 if self.db and key in self.db:
43 return self.db[key].decode('utf-8')43 return self.db[key].decode('utf-8')
44 else:44 return None
45 return None
4645
4746
48class FlatDatabase(object):47class FlatDatabase(object):
4948
50 def __init__(self, filename):49 def __init__(self, filename):
51 self.rows = []50 self.cmd_to_pkgs = {}
52 with open(filename) as dbfile:51 with open(filename) as dbfile:
53 for line in (line.strip() for line in dbfile):52 for line in dbfile:
54 self.rows.append(line.split("|"))53 line = line.strip()
5554 cmd, pkgs = line.split(":", 2)
56 def lookup(self, column, text):55 self.cmd_to_pkgs[cmd] = pkgs
57 result = []56
58 for row in self.rows:57 def lookup(self, key):
59 if row[column] == text:58 if key in self.cmd_to_pkgs:
60 result.append(row)59 return self.cmd_to_pkgs[key]
61 return result60 return None
62
63 def createColumnByCallback(self, cb, column):
64 for row in self.rows:
65 row.append(cb(row[column]))
66
67 def lookupWithCallback(self, column, cb, text):
68 result = []
69 for row in self.rows:
70 if cb(row[column], text):
71 result.append(row)
72 return result
7361
7462
75class ProgramDatabase(object):63class ProgramDatabase(object):
7664
77 (PACKAGE, BASENAME_PATH) = range(2)65 (PACKAGE, BASENAME_PATH) = range(2)
7866
79 def __init__(self, filename):67 def __init__(self, filename, pkgmanager):
68 if filename.endswith(".db"):
69 self.db = BinaryDatabase(filename)
70 elif filename.endswith(".txt"):
71 self.db = FlatDatabase(filename)
72 else:
73 raise Exception("unsupported database %s" % filename)
74 self.pkgmanager = pkgmanager
80 basename = os.path.basename(filename)75 basename = os.path.basename(filename)
81 (self.arch, self.component) = basename.split(".")[0].split("-")76 self.arch, self.component = basename.split(".")[0].split("-")
82 self.db = BinaryDatabase(filename)
8377
84 def lookup(self, command):78 def lookup(self, command):
85 result = self.db.lookup(command)79 result = self.db.lookup(command)
@@ -107,6 +101,7 @@
107class CommandNotFound(object):101class CommandNotFound(object):
108102
109 programs_dir = "programs.d"103 programs_dir = "programs.d"
104 snaps_dir = "snaps.d"
110105
111 prefixes = (106 prefixes = (
112 "/bin",107 "/bin",
@@ -128,8 +123,10 @@
128 'non-free', 'multiverse']123 'non-free', 'multiverse']
129 self.components.reverse()124 self.components.reverse()
130 self.sources_list = self._getSourcesList()125 self.sources_list = self._getSourcesList()
131 for filename in os.listdir(os.path.sep.join([data_dir, self.programs_dir])):126 for filename in os.listdir(os.path.join(data_dir, self.programs_dir)):
132 self.programs.append(ProgramDatabase(os.path.sep.join([data_dir, self.programs_dir, filename])))127 self.programs.append(ProgramDatabase(os.path.join(data_dir, self.programs_dir, filename), "apt"))
128 for fn in os.listdir(os.path.join(data_dir, self.snaps_dir)):
129 self.programs.append(ProgramDatabase(os.path.join(data_dir, self.snaps_dir, fn), "snap"))
133 try:130 try:
134 self.user_can_sudo = grp.getgrnam("sudo")[2] in posix.getgroups() or grp.getgrnam("admin")[2] in posix.getgroups()131 self.user_can_sudo = grp.getgrnam("sudo")[2] in posix.getgroups() or grp.getgrnam("admin")[2] in posix.getgroups()
135 except KeyError:132 except KeyError:
@@ -141,7 +138,7 @@
141 return138 return
142 possible_alternatives = []139 possible_alternatives = []
143 for w in similar_words(word):140 for w in similar_words(word):
144 packages = self.getPackages(w)141 packages = self.get_packages(w)
145 for (package, comp) in packages:142 for (package, comp) in packages:
146 possible_alternatives.append((w, package, comp))143 possible_alternatives.append((w, package, comp))
147 if len(possible_alternatives) > max_alt:144 if len(possible_alternatives) > max_alt:
@@ -151,9 +148,11 @@
151 for (w, p, c) in possible_alternatives:148 for (w, p, c) in possible_alternatives:
152 print(_(" Command '%s' from package '%s' (%s)") % (w, p, c), file=sys.stderr)149 print(_(" Command '%s' from package '%s' (%s)") % (w, p, c), file=sys.stderr)
153150
154 def getPackages(self, command):151 def get_packages(self, command, pkgmanager):
155 result = set()152 result = set()
156 for db in self.programs:153 for db in self.programs:
154 if db.pkgmanager != pkgmanager:
155 continue
157 result.update([(pkg, db.component) for pkg in db.lookup(command)])156 result.update([(pkg, db.component) for pkg in db.lookup(command)])
158 return list(result)157 return list(result)
159158
@@ -263,35 +262,57 @@
263262
264 if command in self.getBlacklist():263 if command in self.getBlacklist():
265 return False264 return False
266 packages = self.getPackages(command)265
267 if len(packages) == 0:266 packages_apt = self.get_packages(command, "apt")
267 packages_snap = self.get_packages(command, "snap")
268 if len(packages_apt) == 0 and len(packages_snap) == 0:
268 self.print_spelling_suggestion(command)269 self.print_spelling_suggestion(command)
269 elif len(packages) == 1:270 return
271 self.advice_on_apt(command, packages_apt)
272 # advice on snaps as well
273 if len(packages_snap) > 0:
274 pkgmanager="snap"
275 print()
276 print(_("It is also available as a snap package:"))
277 for package in packages_snap:
278 print(" * %s" % package[0])
279 if posix.geteuid() == 0:
280 print(_("Try: %s <selected package>") % "%s install" % pkgmanager, file=sys.stderr)
281 elif self.user_can_sudo:
282 print(_("Try: %s <selected package>") % "sudo %s install" % pkgmanager, file=sys.stderr)
283 else:
284 print(_("Ask your administrator to install one of them"), file=sys.stderr)
285 return len(packages_apt) + len(packages_snap)
286
287 def advice_on_apt(self, command, packages):
288 pkgmanager = "apt"
289 if len(packages) == 1:
270 print(_("The program '%s' is currently not installed. ") % command, end="", file=sys.stderr)290 print(_("The program '%s' is currently not installed. ") % command, end="", file=sys.stderr)
271 if posix.geteuid() == 0:291 if posix.geteuid() == 0:
272 print(_("You can install it by typing:"), file=sys.stderr)292 print(_("You can install it by typing:"), file=sys.stderr)
273 print("apt install %s" % packages[0][0], file=sys.stderr)293 print("%s install %s" % (packages[0][0], pkgmanager), file=sys.stderr)
274 self.install_prompt(packages[0][0])294 self.install_prompt(packages[0][0])
275 elif self.user_can_sudo:295 elif self.user_can_sudo:
276 print(_("You can install it by typing:"), file=sys.stderr)296 print(_("You can install it by typing:"), file=sys.stderr)
277 print("sudo apt install %s" % packages[0][0], file=sys.stderr)297 print("sudo %s install %s" % (pkgmanager, packages[0][0]), file=sys.stderr)
278 self.install_prompt(packages[0][0])298 self.install_prompt(packages[0][0])
279 else:299 else:
280 print(_("To run '%(command)s' please ask your administrator to install the package '%(package)s'") % {'command': command, 'package': packages[0][0]}, file=sys.stderr)300 print(_("To run '%(command)s' please ask your administrator to install the %(pkgmanager)s package '%(package)s'") % {'command': command, 'package': packages[0][0], 'pkgmanager': pkgmanager}, file=sys.stderr)
281 if not packages[0][1] in self.sources_list:301 if not packages[0][1] in self.sources_list:
282 print(_("You will have to enable the component called '%s'") % packages[0][1], file=sys.stderr)302 print(_("You will have to enable the component called '%s'") % packages[0][1], file=sys.stderr)
283 elif len(packages) > 1:303 elif len(packages) > 1:
284 packages.sort(key=cmp_to_key(self.sortByComponent))304 packages.sort(key=cmp_to_key(self.sortByComponent))
285 print(_("The program '%s' can be found in the following packages:") % command, file=sys.stderr)305 print(_("The program '%s' can be found in the following packages:") % command, file=sys.stderr)
306 # FIXME: deal with mixed apt/snap results
286 for package in packages:307 for package in packages:
287 if package[1] in self.sources_list:308 if package[1] in self.sources_list:
288 print(" * %s" % package[0], file=sys.stderr)309 print(" * %s" % package[0], file=sys.stderr)
289 else:310 else:
290 print(" * %s" % package[0] + " (" + _("You will have to enable component called '%s'") % package[1] + ")", file=sys.stderr)311 print(" * %s" % package[0] + " (" + _("You will have to enable component called '%s'") % package[1] + ")", file=sys.stderr)
291 if posix.geteuid() == 0:312 if posix.geteuid() == 0:
292 print(_("Try: %s <selected package>") % "apt install", file=sys.stderr)313 print(_("Try: %s <selected package>") % "%s install" % pkgmanager, file=sys.stderr)
293 elif self.user_can_sudo:314 elif self.user_can_sudo:
294 print(_("Try: %s <selected package>") % "sudo apt install", file=sys.stderr)315 print(_("Try: %s <selected package>") % "sudo %s install" % pkgmanager, file=sys.stderr)
295 else:316 else:
296 print(_("Ask your administrator to install one of them"), file=sys.stderr)317 print(_("Ask your administrator to install one of them"), file=sys.stderr)
297 return len(packages) > 0318 return len(packages) > 0
298319
=== modified file 'command-not-found'
--- command-not-found 2016-06-17 14:51:57 +0000
+++ command-not-found 2017-12-21 11:57:34 +0000
@@ -25,7 +25,7 @@
25 from optparse import OptionParser25 from optparse import OptionParser
2626
27 from CommandNotFound.util import crash_guard27 from CommandNotFound.util import crash_guard
28 from CommandNotFound import CommandNotFound28 from CommandNotFound.CommandNotFound import CommandNotFound
29except KeyboardInterrupt:29except KeyboardInterrupt:
30 import sys30 import sys
31 sys.exit(127)31 sys.exit(127)
3232
=== modified file 'debian/dirs'
--- debian/dirs 2017-08-30 13:32:56 +0000
+++ debian/dirs 2017-12-21 11:57:34 +0000
@@ -1,1 +1,3 @@
1usr/bin1usr/bin
2usr/share/command-not-found/programs.d
3usr/share/command-not-found/snaps.d

Subscribers

People subscribed via source and target branches

to all changes: