-
Notifications
You must be signed in to change notification settings - Fork 2
/
command-not-found
executable file
·144 lines (126 loc) · 4.44 KB
/
command-not-found
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
#!/usr/bin/python3
# Copyright 2024 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2
#
# Loosely inspired by Ubuntu/Debian's command-not-found
# (c) Zygmunt Krynicki 2005, 2006, 2007, 2008
import os
import shutil
import subprocess
import sys
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 main():
if len(sys.argv) < 2:
print("Need at least one argument")
return
cmd = sys.argv[1]
# Sanity check to avoid spamming pfl with nonsense. A valid command
# contains letters and may contain numbers, but it may not contain only
# numbers. ".-_" in commands are allowed, but not at the begining or end.
allow_list = ".-_"
allowed_char_at_illegal_pos = False
cmd_cleaned = cmd
for char in allow_list:
cmd_cleaned = cmd_cleaned.replace(char, "")
if cmd.startswith(char) or cmd.endswith(char):
allowed_char_at_illegal_pos = True
if (
not cmd_cleaned.isalnum() or
cmd_cleaned.isnumeric() or
allowed_char_at_illegal_pos
):
print(f"\nCommand \"{cmd}\" does not look like a valid command\n")
return
snap = "/usr/bin/snap"
if os.path.exists(snap):
try:
output = subprocess.check_output(
[snap, "advise-snap", "--command", cmd],
stderr=subprocess.DEVNULL,
universal_newlines=True)
except subprocess.CalledProcessError:
print(f"\nCommand \"{cmd}\" not found\n\n")
else:
print(output)
else:
print(f"\nCommand \"{cmd}\" not found\n\n")
spelling_candidates = []
for word in similar_words(cmd):
if shutil.which(word) is not None:
spelling_candidates += [word]
if len(spelling_candidates) > 0:
print("Maybe you meant one of these commands:")
for candidate in spelling_candidates:
print("\t- " + candidate)
print("\n")
paths = [
"/bin",
"/usr/bin",
"/sbin",
"/usr/sbin",
"/opt/bin"
]
try:
# Trying using e_file as module directly (app-portage/pfl>3.4)
import pfl.e_file
except ModuleNotFoundError:
# If that doesn't work call it via subprocess instead (app-portage/pfl<=3.4)
e_file = "/usr/bin/e-file"
if os.path.exists(e_file):
print(f"Searching the Portage File List for packages installing {cmd}...\n")
found = False
for path in paths:
x = path + "/" + cmd
try:
output = subprocess.check_output(
[e_file, x],
stderr=subprocess.DEVNULL,
universal_newlines=True)
except subprocess.CalledProcessError:
# Unfortunatly there is no distinction in error code between fail and not found
pass
else:
found = True
print(output)
if found:
print("")
else:
print("No candidates found\n\n")
else:
print(f"Searching the Portage File List for packages installing {cmd}...\n")
found = False
for path in paths:
x = path + "/" + cmd
try:
options = {
'file': x,
'outputPlain': False,
}
ret, out = pfl.e_file.run(options)
except SystemExit:
# If something is not working just quit
break
else:
if ret == 0:
found = True
sys.stdout.write(out)
if found:
print("")
else:
print("No candidates found\n\n")
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt as e:
pass