Merge lp:~mvo/command-not-found/snap-support into lp:command-not-found
- snap-support
- Merge into ubuntu
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ubuntu Core Development Team | Pending | ||
Review via email: mp+335516@code.launchpad.net |
Commit message
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/
To post a comment you must log in.
Revision history for this message
Brian Murray (brian-murray) wrote : | # |
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
1 | === modified file 'CommandNotFound/CommandNotFound.py' | |||
2 | --- CommandNotFound/CommandNotFound.py 2017-12-21 10:34:37 +0000 | |||
3 | +++ CommandNotFound/CommandNotFound.py 2017-12-21 11:57:34 +0000 | |||
4 | @@ -41,45 +41,39 @@ | |||
5 | 41 | key = key.encode('utf-8', 'xmlcharrefreplace') | 41 | key = key.encode('utf-8', 'xmlcharrefreplace') |
6 | 42 | if self.db and key in self.db: | 42 | if self.db and key in self.db: |
7 | 43 | return self.db[key].decode('utf-8') | 43 | return self.db[key].decode('utf-8') |
10 | 44 | else: | 44 | return None |
9 | 45 | return None | ||
11 | 46 | 45 | ||
12 | 47 | 46 | ||
13 | 48 | class FlatDatabase(object): | 47 | class FlatDatabase(object): |
14 | 49 | 48 | ||
15 | 50 | def __init__(self, filename): | 49 | def __init__(self, filename): |
17 | 51 | self.rows = [] | 50 | self.cmd_to_pkgs = {} |
18 | 52 | with open(filename) as dbfile: | 51 | with open(filename) as dbfile: |
39 | 53 | for line in (line.strip() for line in dbfile): | 52 | for line in dbfile: |
40 | 54 | self.rows.append(line.split("|")) | 53 | line = line.strip() |
41 | 55 | 54 | cmd, pkgs = line.split(":", 2) | |
42 | 56 | def lookup(self, column, text): | 55 | self.cmd_to_pkgs[cmd] = pkgs |
43 | 57 | result = [] | 56 | |
44 | 58 | for row in self.rows: | 57 | def lookup(self, key): |
45 | 59 | if row[column] == text: | 58 | if key in self.cmd_to_pkgs: |
46 | 60 | result.append(row) | 59 | return self.cmd_to_pkgs[key] |
47 | 61 | return result | 60 | return None |
28 | 62 | |||
29 | 63 | def createColumnByCallback(self, cb, column): | ||
30 | 64 | for row in self.rows: | ||
31 | 65 | row.append(cb(row[column])) | ||
32 | 66 | |||
33 | 67 | def lookupWithCallback(self, column, cb, text): | ||
34 | 68 | result = [] | ||
35 | 69 | for row in self.rows: | ||
36 | 70 | if cb(row[column], text): | ||
37 | 71 | result.append(row) | ||
38 | 72 | return result | ||
48 | 73 | 61 | ||
49 | 74 | 62 | ||
50 | 75 | class ProgramDatabase(object): | 63 | class ProgramDatabase(object): |
51 | 76 | 64 | ||
52 | 77 | (PACKAGE, BASENAME_PATH) = range(2) | 65 | (PACKAGE, BASENAME_PATH) = range(2) |
53 | 78 | 66 | ||
55 | 79 | def __init__(self, filename): | 67 | def __init__(self, filename, pkgmanager): |
56 | 68 | if filename.endswith(".db"): | ||
57 | 69 | self.db = BinaryDatabase(filename) | ||
58 | 70 | elif filename.endswith(".txt"): | ||
59 | 71 | self.db = FlatDatabase(filename) | ||
60 | 72 | else: | ||
61 | 73 | raise Exception("unsupported database %s" % filename) | ||
62 | 74 | self.pkgmanager = pkgmanager | ||
63 | 80 | basename = os.path.basename(filename) | 75 | basename = os.path.basename(filename) |
66 | 81 | (self.arch, self.component) = basename.split(".")[0].split("-") | 76 | self.arch, self.component = basename.split(".")[0].split("-") |
65 | 82 | self.db = BinaryDatabase(filename) | ||
67 | 83 | 77 | ||
68 | 84 | def lookup(self, command): | 78 | def lookup(self, command): |
69 | 85 | result = self.db.lookup(command) | 79 | result = self.db.lookup(command) |
70 | @@ -107,6 +101,7 @@ | |||
71 | 107 | class CommandNotFound(object): | 101 | class CommandNotFound(object): |
72 | 108 | 102 | ||
73 | 109 | programs_dir = "programs.d" | 103 | programs_dir = "programs.d" |
74 | 104 | snaps_dir = "snaps.d" | ||
75 | 110 | 105 | ||
76 | 111 | prefixes = ( | 106 | prefixes = ( |
77 | 112 | "/bin", | 107 | "/bin", |
78 | @@ -128,8 +123,10 @@ | |||
79 | 128 | 'non-free', 'multiverse'] | 123 | 'non-free', 'multiverse'] |
80 | 129 | self.components.reverse() | 124 | self.components.reverse() |
81 | 130 | self.sources_list = self._getSourcesList() | 125 | self.sources_list = self._getSourcesList() |
84 | 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)): |
85 | 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")) |
86 | 128 | for fn in os.listdir(os.path.join(data_dir, self.snaps_dir)): | ||
87 | 129 | self.programs.append(ProgramDatabase(os.path.join(data_dir, self.snaps_dir, fn), "snap")) | ||
88 | 133 | try: | 130 | try: |
89 | 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() |
90 | 135 | except KeyError: | 132 | except KeyError: |
91 | @@ -141,7 +138,7 @@ | |||
92 | 141 | return | 138 | return |
93 | 142 | possible_alternatives = [] | 139 | possible_alternatives = [] |
94 | 143 | for w in similar_words(word): | 140 | for w in similar_words(word): |
96 | 144 | packages = self.getPackages(w) | 141 | packages = self.get_packages(w) |
97 | 145 | for (package, comp) in packages: | 142 | for (package, comp) in packages: |
98 | 146 | possible_alternatives.append((w, package, comp)) | 143 | possible_alternatives.append((w, package, comp)) |
99 | 147 | if len(possible_alternatives) > max_alt: | 144 | if len(possible_alternatives) > max_alt: |
100 | @@ -151,9 +148,11 @@ | |||
101 | 151 | for (w, p, c) in possible_alternatives: | 148 | for (w, p, c) in possible_alternatives: |
102 | 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) |
103 | 153 | 150 | ||
105 | 154 | def getPackages(self, command): | 151 | def get_packages(self, command, pkgmanager): |
106 | 155 | result = set() | 152 | result = set() |
107 | 156 | for db in self.programs: | 153 | for db in self.programs: |
108 | 154 | if db.pkgmanager != pkgmanager: | ||
109 | 155 | continue | ||
110 | 157 | result.update([(pkg, db.component) for pkg in db.lookup(command)]) | 156 | result.update([(pkg, db.component) for pkg in db.lookup(command)]) |
111 | 158 | return list(result) | 157 | return list(result) |
112 | 159 | 158 | ||
113 | @@ -263,35 +262,57 @@ | |||
114 | 263 | 262 | ||
115 | 264 | if command in self.getBlacklist(): | 263 | if command in self.getBlacklist(): |
116 | 265 | return False | 264 | return False |
119 | 266 | packages = self.getPackages(command) | 265 | |
120 | 267 | if len(packages) == 0: | 266 | packages_apt = self.get_packages(command, "apt") |
121 | 267 | packages_snap = self.get_packages(command, "snap") | ||
122 | 268 | if len(packages_apt) == 0 and len(packages_snap) == 0: | ||
123 | 268 | self.print_spelling_suggestion(command) | 269 | self.print_spelling_suggestion(command) |
125 | 269 | elif len(packages) == 1: | 270 | return |
126 | 271 | self.advice_on_apt(command, packages_apt) | ||
127 | 272 | # advice on snaps as well | ||
128 | 273 | if len(packages_snap) > 0: | ||
129 | 274 | pkgmanager="snap" | ||
130 | 275 | print() | ||
131 | 276 | print(_("It is also available as a snap package:")) | ||
132 | 277 | for package in packages_snap: | ||
133 | 278 | print(" * %s" % package[0]) | ||
134 | 279 | if posix.geteuid() == 0: | ||
135 | 280 | print(_("Try: %s <selected package>") % "%s install" % pkgmanager, file=sys.stderr) | ||
136 | 281 | elif self.user_can_sudo: | ||
137 | 282 | print(_("Try: %s <selected package>") % "sudo %s install" % pkgmanager, file=sys.stderr) | ||
138 | 283 | else: | ||
139 | 284 | print(_("Ask your administrator to install one of them"), file=sys.stderr) | ||
140 | 285 | return len(packages_apt) + len(packages_snap) | ||
141 | 286 | |||
142 | 287 | def advice_on_apt(self, command, packages): | ||
143 | 288 | pkgmanager = "apt" | ||
144 | 289 | if len(packages) == 1: | ||
145 | 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) |
146 | 271 | if posix.geteuid() == 0: | 291 | if posix.geteuid() == 0: |
147 | 272 | print(_("You can install it by typing:"), file=sys.stderr) | 292 | print(_("You can install it by typing:"), file=sys.stderr) |
149 | 273 | print("apt install %s" % packages[0][0], file=sys.stderr) | 293 | print("%s install %s" % (packages[0][0], pkgmanager), file=sys.stderr) |
150 | 274 | self.install_prompt(packages[0][0]) | 294 | self.install_prompt(packages[0][0]) |
151 | 275 | elif self.user_can_sudo: | 295 | elif self.user_can_sudo: |
152 | 276 | print(_("You can install it by typing:"), file=sys.stderr) | 296 | print(_("You can install it by typing:"), file=sys.stderr) |
154 | 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) |
155 | 278 | self.install_prompt(packages[0][0]) | 298 | self.install_prompt(packages[0][0]) |
156 | 279 | else: | 299 | else: |
158 | 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) |
159 | 281 | if not packages[0][1] in self.sources_list: | 301 | if not packages[0][1] in self.sources_list: |
160 | 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) |
161 | 283 | elif len(packages) > 1: | 303 | elif len(packages) > 1: |
162 | 284 | packages.sort(key=cmp_to_key(self.sortByComponent)) | 304 | packages.sort(key=cmp_to_key(self.sortByComponent)) |
163 | 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) |
164 | 306 | # FIXME: deal with mixed apt/snap results | ||
165 | 286 | for package in packages: | 307 | for package in packages: |
166 | 287 | if package[1] in self.sources_list: | 308 | if package[1] in self.sources_list: |
167 | 288 | print(" * %s" % package[0], file=sys.stderr) | 309 | print(" * %s" % package[0], file=sys.stderr) |
168 | 289 | else: | 310 | else: |
169 | 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) |
170 | 291 | if posix.geteuid() == 0: | 312 | if posix.geteuid() == 0: |
172 | 292 | print(_("Try: %s <selected package>") % "apt install", file=sys.stderr) | 313 | print(_("Try: %s <selected package>") % "%s install" % pkgmanager, file=sys.stderr) |
173 | 293 | elif self.user_can_sudo: | 314 | elif self.user_can_sudo: |
175 | 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) |
176 | 295 | else: | 316 | else: |
177 | 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) |
178 | 297 | return len(packages) > 0 | 318 | return len(packages) > 0 |
179 | 298 | 319 | ||
180 | === modified file 'command-not-found' | |||
181 | --- command-not-found 2016-06-17 14:51:57 +0000 | |||
182 | +++ command-not-found 2017-12-21 11:57:34 +0000 | |||
183 | @@ -25,7 +25,7 @@ | |||
184 | 25 | from optparse import OptionParser | 25 | from optparse import OptionParser |
185 | 26 | 26 | ||
186 | 27 | from CommandNotFound.util import crash_guard | 27 | from CommandNotFound.util import crash_guard |
188 | 28 | from CommandNotFound import CommandNotFound | 28 | from CommandNotFound.CommandNotFound import CommandNotFound |
189 | 29 | except KeyboardInterrupt: | 29 | except KeyboardInterrupt: |
190 | 30 | import sys | 30 | import sys |
191 | 31 | sys.exit(127) | 31 | sys.exit(127) |
192 | 32 | 32 | ||
193 | === modified file 'debian/dirs' | |||
194 | --- debian/dirs 2017-08-30 13:32:56 +0000 | |||
195 | +++ debian/dirs 2017-12-21 11:57:34 +0000 | |||
196 | @@ -1,1 +1,3 @@ | |||
197 | 1 | usr/bin | 1 | usr/bin |
198 | 2 | usr/share/command-not-found/programs.d | ||
199 | 3 | usr/share/command-not-found/snaps.d |
I believe this landed in Bionic.
command-not-found (0.3ubuntu18. 04.0~pre3) bionic; urgency=medium
* CommandNotFound /CommandNotFoun d.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