command-not-found: handle multiple matches at same length
[termux-packages] / packages / command-not-found / command-not-found.c
CommitLineData
284604c7
FF
1#include <stdio.h>
2#include <string.h>
3#include <stdbool.h>
4
5#define STRINGIFY(x) #x
6#define TOSTRING(x) STRINGIFY(x)
7
8inline int termux_min3(unsigned int a, unsigned int b, unsigned int c) {
9 return (a < b ? (a < c ? a : c) : (b < c ? b : c));
10}
11
12int termux_levenshtein_distance(char const* restrict s1, char const* restrict s2) {
13 unsigned int s1len = strlen(s1);
14 unsigned int s2len = strlen(s2);
15 unsigned int matrix[s2len+1][s1len+1];
16 matrix[0][0] = 0;
17 for (unsigned x = 1; x <= s2len; x++)
18 matrix[x][0] = matrix[x-1][0] + 1;
19 for (unsigned y = 1; y <= s1len; y++)
20 matrix[0][y] = matrix[0][y-1] + 1;
21 for (unsigned x = 1; x <= s2len; x++)
22 for (unsigned y = 1; y <= s1len; y++)
23 matrix[x][y] = termux_min3(matrix[x-1][y] + 1, matrix[x][y-1] + 1, matrix[x-1][y-1] + (s1[y-1] == s2[x-1] ? 0 : 1));
24 return matrix[s2len][s1len];
25}
26
27int main(int argc, char** argv) {
28 if (argc != 2) {
29 fprintf(stderr, "usage: termux-command-not-found <command>\n");
30 return 1;
31 }
32 char* command_not_found = argv[1];
33
34 FILE* commands_file = fopen(TOSTRING(TERMUX_COMMANDS_LISTING), "r");
35 if (commands_file == NULL) {
36 perror(TOSTRING(TERMUX_COMMANDS_LISTING));
37 return 1;
38 }
39
40 int best_distance = -1;
41 int guesses_at_best_distance = 0;
42 char current_package[128];
43 char best_package_guess[128];
44 char best_command_guess[128];
45 char* current_line = NULL;
46 while (true) {
47 size_t buffer_length = sizeof(current_line);
48 ssize_t read_bytes = getline(&current_line, &buffer_length, commands_file);
49 if (read_bytes <= 1) break;
50 size_t line_length = strlen(current_line);
51 current_line[line_length-1] = 0;
52 if (current_line[0] == ' ') { // Binary
53 char* binary_name = current_line + 1;
54 int distance = termux_levenshtein_distance(command_not_found, binary_name);
55 if (distance == 0 && strcmp(command_not_found, binary_name) == 0) {
56 printf("The program '%s' is currently not installed. You can install it by executing:\n apt install %s\n", binary_name, current_package);
57 return 0;
58 } else if (best_distance == distance) {
59 guesses_at_best_distance++;
60 } else if (best_distance == -1 || best_distance > distance) {
61 guesses_at_best_distance = 0;
62 best_distance = distance;
63 strncpy(best_command_guess, binary_name, sizeof(best_command_guess));
64 strncpy(best_package_guess, current_package, sizeof(best_package_guess));
65 }
66 } else { // Package
67 strncpy(current_package, current_line, sizeof(current_package));
68 }
69 }
70
71 if (best_distance == -1 || best_distance > 3) {
72 printf("%s: command not found\n", command_not_found);
73 } else {
74 printf("No command '%s' found, did you mean:\n", command_not_found);
6ee3449e
FF
75 if (guesses_at_best_distance == 1) {
76 // Only one suggestion - show it:
77 printf(" Command '%s' from package '%s'\n", best_command_guess, best_package_guess);
78 } else {
79 // Multiple suggestions at the same distance - show them all:
80 rewind(commands_file);
81 while (true) {
82 size_t buffer_length = sizeof(current_line);
83 ssize_t read_bytes = getline(&current_line, &buffer_length, commands_file);
84 if (read_bytes <= 1) break;
85 size_t line_length = strlen(current_line);
86 current_line[line_length-1] = 0;
87 if (current_line[0] == ' ') { // Binary
88 char* binary_name = current_line + 1;
89 int distance = termux_levenshtein_distance(command_not_found, binary_name);
90 if (best_distance == distance) {
91 printf(" Command '%s' from package '%s'\n", binary_name, current_package);
92 }
93 } else { // Package
94 strncpy(current_package, current_line, sizeof(current_package));
95 }
96 }
97 }
284604c7
FF
98 }
99}
100