Skip to content

Commit 65e35ce

Browse files
committed
refactor
1 parent 43f5cd6 commit 65e35ce

File tree

3 files changed

+169
-115
lines changed

3 files changed

+169
-115
lines changed

include/Settings.hpp

+31-14
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ using json = nlohmann::json;
1818
#define DATABASE_FILENAME "historyStorage.db"
1919
#define DEFAULT_DATABASE_HISTORY_STORAGE true
2020
#define DEFAULT_IGNORE_MNT_FROM_SYSTEM_PATH_VARIABLES true
21+
#define DEFAULT_LENGTH_CONDITION_ENABLED true
22+
#define DEFAULT_LENGTH_CONDITION_HEURISTIC 2
2123

2224
#define DEBUG false
2325

@@ -40,6 +42,8 @@ class Settings {
4042

4143
bool databaseHistoryStorageEnabled;
4244
bool ignoreMntFromSystemPathVariables;
45+
bool lengthConditionHeuristicEnabled;
46+
int lengthConditionHeuristic;
4347
std::vector<std::string> systemPathVariableList;
4448
SQLite::Database * db;
4549

@@ -79,6 +83,9 @@ class Settings {
7983

8084
settingsFile["ignoreMntFromSystemPathVariables"] = DEFAULT_IGNORE_MNT_FROM_SYSTEM_PATH_VARIABLES;
8185

86+
settingsFile["lengthConditionHeuristicEnabled"] = DEFAULT_LENGTH_CONDITION_ENABLED;
87+
settingsFile["lengthConditionHeuristic"] = DEFAULT_LENGTH_CONDITION_HEURISTIC;
88+
8289
std::ofstream file(settingsFilePath);
8390
file<<settingsFile;
8491
file.close();
@@ -91,24 +98,30 @@ class Settings {
9198
}
9299

93100
void loadSettingsFile() {
94-
spdlog::info("Loding settings file configuration...");
101+
try {
102+
spdlog::info("Loding settings file configuration...");
95103

96-
std::ifstream file(settingsFilePath);
97-
if (!file.is_open()) {
98-
spdlog::error("Error while opening settings file: " + settingsDirectoryPath.string() + "/" + settingsFileName);
99-
exit(1);
100-
}
104+
std::ifstream file(settingsFilePath);
105+
if (!file.is_open()) {
106+
spdlog::error("Error while opening settings file: " + settingsDirectoryPath.string() + "/" + settingsFileName);
107+
exit(1);
108+
}
101109

102-
file>>settingsFile;
103-
file.close();
110+
file>>settingsFile;
111+
file.close();
104112

105-
ignoreMntFromSystemPathVariables = settingsFile["ignoreMntFromSystemPathVariables"].get<bool>();
106-
systemPathVariableList = settingsFile["systemBinariesPath"].get<std::vector<std::string>>();
113+
ignoreMntFromSystemPathVariables = settingsFile["ignoreMntFromSystemPathVariables"].get<bool>();
114+
systemPathVariableList = settingsFile["systemBinariesPath"].get<std::vector<std::string>>();
115+
lengthConditionHeuristicEnabled = settingsFile["lengthConditionHeuristicEnabled"].get<bool>();
116+
lengthConditionHeuristic = settingsFile["lengthConditionHeuristic"].get<int>();
107117

108-
databaseHistoryStorageEnabled = settingsFile["databaseHistoryStorageEnabled"].get<bool>();
109-
generateDatabaseIfNotExists(databaseHistoryStorageEnabled);
110-
111-
spdlog::info("Settings file successfully loaded");
118+
databaseHistoryStorageEnabled = settingsFile["databaseHistoryStorageEnabled"].get<bool>();
119+
generateDatabaseIfNotExists(databaseHistoryStorageEnabled);
120+
121+
spdlog::info("Settings file successfully loaded");
122+
} catch (const std::exception &e) {
123+
spdlog::error("Error while loading the settings file: {}", e.what());
124+
}
112125
}
113126

114127
void generateDatabaseIfNotExists(bool databaseHistoryStorageEnabled) {
@@ -180,4 +193,8 @@ class Settings {
180193
} else
181194
return systemPathVariableList;
182195
}
196+
197+
// By adding const to these member functions, it promises that calling them will not change the state of the Settings object
198+
bool getLengthConditionHeuristicEnabled() const { return lengthConditionHeuristicEnabled; }
199+
int getLengthConditionHeuristic() const { return lengthConditionHeuristic; }
183200
};

initializer/initialzer.sh

+1-4
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
#!/bin/bash
22

3-
echo "Initializer script: checking if the command_not_found_handle is in the .bashrc file..."
3+
# Checking if the command_not_found_handle is in the .bashrc file
44
if ! grep -q 'command_not_found_handle' ~/.bashrc; then
5-
echo "Initializer script: adding the command_not_found_handle in the .bashrc file. Appending it..."
65
cat ./initializer/command_not_found_handle.sh >> ~/.bashrc
7-
else
8-
echo "Initializer script: the command_not_found_handle is already present in the .bashrc file"
96
fi

src/main.cpp

+137-97
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,156 @@
1010

1111
namespace po = boost::program_options;
1212

13+
/**
14+
* Compares the given input command against binaries found in the system path, applying, if enabled a heuristic
15+
* based on the difference in length and character similarities between the input command and system binaries,
16+
* which filters those whose difference with the input command exceeds a threshold.
17+
* Calculates the word distances with the Damerau-Leveshtein algorithm and suggests the closest matches.
18+
*
19+
* @param vm Boost program options variables map that holds command line options.
20+
* @param inputCommand The command entered by the user, which needs to be matched or corrected.
21+
* @param settings The settings object containing configuration such as system path variables and heuristics for matching.
22+
* @return true if similar commands are found, otherwise false if no suggestions can be made.
23+
*/
24+
bool suggestCommands(const po::variables_map vm, std::string inputCommand, Settings settings) {
25+
spdlog::info("Printing content of system path vector");
26+
27+
// If verbose mode enabled print the content of the system path vector
28+
if (vm.count("v"))
29+
CommonUtils::printVector(settings.getSystemPathVariablePaths());
30+
31+
// Moving system path to a new data structure
32+
std::set<std::string> systemPathVariableSet;
33+
for (const auto &entry : settings.getSystemPathVariablePaths()) {
34+
std::vector<std::string> filesInPath = CommonUtils::getListOfFilesInPath(entry, false, true);
35+
systemPathVariableSet.insert(filesInPath.begin(), filesInPath.end());
36+
}
37+
38+
spdlog::info("Printing content of system path set");
39+
40+
// If verbose mode enabled print the set
41+
if (vm.count("v"))
42+
CommonUtils::printSet(systemPathVariableSet);
43+
44+
spdlog::info("Before filtering set size: {}", systemPathVariableSet.size());
45+
46+
std::set<std::string> systemPathVariableFilteredSet;
47+
48+
spdlog::info("Applying length distance heuristic based on a maximum difference in length of {}", settings.getLengthConditionHeuristic());
49+
if (settings.getLengthConditionHeuristicEnabled()) {
50+
51+
std::copy_if(
52+
systemPathVariableSet.begin(),
53+
systemPathVariableSet.end(),
54+
std::inserter(systemPathVariableFilteredSet, systemPathVariableFilteredSet.end()),
55+
[inputCommand, settings](const std::string& value){
56+
57+
int absoluteLengthLetterDifference = std::abs(static_cast<int>(value.length()) - static_cast<int>(inputCommand.length()));
58+
59+
// This heuristic is used to improve runtimes by reducing the numbers of binaries to check based on the total amount of
60+
// length differencies between the two words. By default, if their length is different by two or more letters, their
61+
// distance do not get calculated
62+
bool lengthCondition = absoluteLengthLetterDifference <= settings.getLengthConditionHeuristic();
63+
64+
if (!lengthCondition)
65+
return lengthCondition;
66+
67+
std::unordered_set<char> inputCommandCharSet = CommonUtils::getSetOfUniqueCharFromString(inputCommand);
68+
std::unordered_set<char> currentBinaryCharSet = CommonUtils::getSetOfUniqueCharFromString(value);
69+
70+
std::unordered_set<char> intersectionSet;
71+
for (char ch : inputCommandCharSet) {
72+
if (currentBinaryCharSet.find(ch) != currentBinaryCharSet.end())
73+
intersectionSet.insert(ch);
74+
}
75+
76+
bool letterCondition = intersectionSet.size() >= (inputCommandCharSet.size() / 2);
77+
78+
return letterCondition;
79+
}
80+
);
81+
} else
82+
systemPathVariableFilteredSet = systemPathVariableSet;
83+
84+
if (settings.getLengthConditionHeuristicEnabled()) {
85+
spdlog::info("After filtering set size: {}", systemPathVariableFilteredSet.size());
86+
87+
spdlog::info("Printing content of system path set after filtering");
88+
// If verbose mode enabled print the newly filtered set
89+
if (vm.count("v"))
90+
CommonUtils::printSet(systemPathVariableFilteredSet);
91+
}
92+
93+
spdlog::info("Calculating distance Map");
94+
std::map<std::string, int> distanceMap = WordDistanceHandler::calculateWordDistance(inputCommand, systemPathVariableFilteredSet);
95+
96+
if (distanceMap.size() == 0) {
97+
std::cout<<"Could not find any similar commands to \""<<inputCommand<<"\"\n";
98+
return false;
99+
}
100+
101+
int minValueInMap = INT_MAX;
102+
for (auto const &entry : distanceMap) {
103+
if (entry.second < minValueInMap)
104+
minValueInMap = entry.second;
105+
}
106+
107+
std::vector<std::string> similarCommands;
108+
for (auto const &entry : distanceMap) {
109+
if (entry.second == minValueInMap) {
110+
similarCommands.push_back(entry.first);
111+
}
112+
}
113+
114+
if (similarCommands.size() > 1) {
115+
std::cout<<"Could not find command " + inputCommand + ". Were you looking for these?\n";
116+
for (auto const &entry : similarCommands)
117+
std::cout<<"- "<<entry<<"\n";
118+
} else
119+
std::cout<<"Could not find command " + inputCommand + ". Were you looking for \"" + similarCommands[0]<<"\"?\n";
120+
121+
return true;
122+
}
123+
124+
/**
125+
* Ensures that the `command_not_found_handle` function is present in the `.bashrc` file by running the initializer script.
126+
*
127+
* @return int Returns 0 if the script executes successfully, otherwise returns a non-zero value on failure.
128+
*/
129+
int ensureCommandNotFoundHandler() {
130+
int result = system("bash initializer/initialzer.sh");
131+
132+
if (result == 0)
133+
spdlog::info("Initializer script successfully executed.");
134+
else
135+
spdlog::error("Error executing the initializer script. Return code: {}", result);
136+
137+
return result;
138+
}
139+
140+
13141
int main(int argc, char* argv[]) {
14142

15143
std::string inputCommand;
144+
145+
po::variables_map vm;
146+
po::options_description desc("Allowed options");
16147

17148
try {
18-
19149
// Handling program parameters
20-
po::options_description desc("Allowed options");
21150
desc.add_options()
22151
("i", po::value<std::string>(), "The input command")
23152
("e", "Edit the configuration file")
153+
("v", "Verbose mode")
24154
("help", "Produce a help message");
25155

26-
po::variables_map vm;
27156
po::store(po::parse_command_line(argc, argv, desc), vm);
28157
po::notify(vm);
29158

30159

160+
if (!vm.count("v")) {
161+
spdlog::set_level(spdlog::level::off);
162+
}
31163

32164
if (vm.count("i")) {
33165
inputCommand = vm["i"].as<std::string>();
@@ -44,8 +176,6 @@ int main(int argc, char* argv[]) {
44176
return 0;
45177
}
46178

47-
48-
49179
}
50180
catch(std::exception& e) {
51181
std::cerr<<"error: "<<e.what()<<"\n";
@@ -57,100 +187,10 @@ int main(int argc, char* argv[]) {
57187
}
58188

59189
Settings settings;
60-
spdlog::info("Settings successufully loaded.");
61-
62-
63-
// Writing command_not_found_handle funtion in .bashrc file if not present
64-
int result = system("bash initializer/initialzer.sh");
65-
if (result != 0)
66-
spdlog::error("Error executing the initializer script. Return code: {}", result);
67-
else
68-
spdlog::info("Initializer script successfully executed.");
69-
70-
spdlog::info("Printing content of system path vector");
71-
CommonUtils::printVector(settings.getSystemPathVariablePaths());
72-
73-
// TODO: write a new program to update the database with each new binaries in the system each time a new bash shell is open
74-
// to reduce calcultion time
75-
76-
77-
78-
std::set<std::string> systemPathVariableSet;
79-
for (const auto &entry : settings.getSystemPathVariablePaths()) {
80-
std::cout<<"Content of: "<<entry<<"\n";
81-
82-
std::vector<std::string> filesInPath = CommonUtils::getListOfFilesInPath(entry, false, true);
83-
84-
systemPathVariableSet.insert(filesInPath.begin(), filesInPath.end());
85-
}
86-
87-
// TODO: use an heuristic to reduce calculation time further
88-
spdlog::info("Printing content of system path set");
89-
CommonUtils::printSet(systemPathVariableSet);
90-
91-
spdlog::info("Before filtering set size: {}", systemPathVariableSet.size());
92-
93-
std::set<std::string> systemPathVariableFilteredSet;
94-
95-
96-
std::copy_if(
97-
systemPathVariableSet.begin(),
98-
systemPathVariableSet.end(),
99-
std::inserter(systemPathVariableFilteredSet, systemPathVariableFilteredSet.end()),
100-
[inputCommand](const std::string& value){
101-
102-
bool lengthCondition = std::abs(static_cast<int>(value.length()) - static_cast<int>(inputCommand.length())) <= 2;
103-
104-
if (!lengthCondition)
105-
return lengthCondition;
106-
107-
std::unordered_set<char> inputCommandCharSet = CommonUtils::getSetOfUniqueCharFromString(inputCommand);
108-
std::unordered_set<char> currentBinaryCharSet = CommonUtils::getSetOfUniqueCharFromString(value);
109-
110-
std::unordered_set<char> intersectionSet;
111-
for (char ch : inputCommandCharSet) {
112-
if (currentBinaryCharSet.find(ch) != currentBinaryCharSet.end()) {
113-
intersectionSet.insert(ch);
114-
}
115-
}
116-
117-
bool letterCondition = intersectionSet.size() >= (inputCommandCharSet.size() / 2);
118-
119-
return letterCondition;
120-
}
121-
);
122-
123-
spdlog::info("After filtering set size: {}", systemPathVariableFilteredSet.size());
124-
125-
spdlog::info("Printing content of system path set after filtering");
126-
CommonUtils::printSet(systemPathVariableFilteredSet);
127-
128-
spdlog::info("Calculating distance Map");
129-
std::map<std::string, int> distanceMap = WordDistanceHandler::calculateWordDistance(inputCommand, systemPathVariableFilteredSet);
130-
131-
int minValueInMap = INT_MAX;
132-
for (auto const &entry : distanceMap) {
133-
if (entry.second < minValueInMap)
134-
minValueInMap = entry.second;
135-
}
136-
137-
std::vector<std::string> similarCommands;
138-
for (auto const &entry : distanceMap) {
139-
if (entry.second == minValueInMap) {
140-
similarCommands.push_back(entry.first);
141-
}
142-
}
143-
144-
std::cout<<"Could not find command " + inputCommand + ". Were you looking for";
145-
for (auto const &entry : similarCommands) {
146-
std::cout<<" \""<<entry<<"\"";
147-
}
148-
std::cout<<"?\n";
149190

191+
ensureCommandNotFoundHandler();
150192

193+
suggestCommands(vm, inputCommand, settings);
151194

152-
//spdlog::info("Printing distance Map");
153-
//WordDistanceHandler::printDistanceMap(distanceMap);
154-
155195
return 0;
156196
}

0 commit comments

Comments
 (0)