I'm writing a program to help with remembering complex bash commands. On invoking the program, it asks for a description of the desired operation, e.g., "increase volume" or "find orphaned packages", and displays the commands matching the input ordered by closest match.
Matching is determined by splitting the command text into a string vector, then the description, and combining these with a list of additional keywords to compare with the input via std::set_intersection
. This is currently case sensitive, which I plan to change.
This is my first C++ program so I've probably made plenty of mistakes. A couple things I'm still unclear on: when to pass arguments by reference, when to use const
and static
, and when to use pointers.
#include <algorithm>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
#include <vector>
struct Command {
std::string text;
std::string description;
std::vector<std::string> addl_keywords;
};
struct FoundCommand {
std::vector<std::string>::size_type keywords_found;
Command command;
FoundCommand(std::vector<std::string>::size_type keywords_found, Command command)
: keywords_found {keywords_found}, command {command} {}
bool operator < (FoundCommand other) {
return keywords_found > other.keywords_found;
}
bool operator > (FoundCommand other) {
return keywords_found < other.keywords_found;
}
};
// TODO: make case insensitive
static const Command commands[] {
{"amixer -Mq set Master 1%-", "decrease volume", {"lower"}},
{"amixer -Mq set Master 1%+", "increase volume", {"raise"}},
{"makepkg -sri", "build and install a package using a PKGBUILD file", {"pkgbuild"}},
{"makepkg -efi", "rebuild and reinstall a package using a PKGBUILD file",
{"build", "install", "pkgbuild"}},
{"pacman -Qdt", "list orphaned packages", {"find", "orphan"}},
{"pacman -Qe", "list explicitly installed packages", {"find", "explicit"}},
{"pacman -Ql [package]", "list files owned by package", {"find", "own"}},
{"pacman -Qo [file]", "list packages that own file", {"find", "owned"}}
};
std::vector<std::string> Split(const std::string& s, char delim = ' ') {
std::stringstream ss {s};
std::string part;
std::vector<std::string> parts;
while (getline(ss, part, delim))
parts.push_back(part);
return parts;
}
std::vector<std::string> Union(std::vector<std::string> v1,
std::vector<std::string> v2) {
std::vector<std::string> result;
result.reserve(v1.size() + v2.size());
result.insert(result.end(), v1.begin(), v1.end());
result.insert(result.end(), v2.begin(), v2.end());
return result;
}
std::vector<std::string> Union(std::vector<std::string> v1,
std::vector<std::string> v2,
std::vector<std::string> v3) {
return Union(Union(v1, v2), v3);
}
std::vector<std::string> Intersect(std::vector<std::string>& v1,
std::vector<std::string>& v2) {
std::sort(v1.begin(), v1.end());
std::sort(v2.begin(), v2.end());
std::vector<std::string> result;
std::set_intersection(v1.begin(), v1.end(),
v2.begin(), v2.end(),
std::back_inserter(result));
return result;
}
std::vector<FoundCommand> FindCommands(std::vector<std::string>& input_keywords) {
std::vector<FoundCommand> results;
for (Command command : commands) {
std::vector<std::string> cmd_keywords = Union(Split(command.text),
Split(command.description),
command.addl_keywords);
std::vector<std::string> found_keywords = Intersect(input_keywords, cmd_keywords);
if (!found_keywords.empty()) {
results.emplace_back(found_keywords.size(), command);
}
}
return results;
}
int main() {
std::cout << "Using keywords, describe what you would like to do: ";
std::string input;
getline(std::cin, input);
std::vector<std::string> input_keywords = Split(input);
std::vector<FoundCommand> results = FindCommands(input_keywords);
std::sort(results.begin(), results.end());
for (FoundCommand result : results)
std::cout << result.command.description << ": " << result.command.text << std::endl;
return 0;
}
set_union
too (instead of your ownUnion
) since you are already usingset_intersection
as well? \$\endgroup\$