// C++ #include #include #include #include #include #include #include #include // POSIX #include #include #include //libraries #include #include #include // project #include "commit.inc" std::vector additions; std::string change; std::map changes; std::vector deletions; bool should_list = false; bool sort = false; static void handle_additions(std::vector& tags) { for (const std::string& tag : additions) { tags.push_back(tag); } } static void handle_changes(std::vector& tags) { for (const auto& elem : changes) { auto it = std::find(tags.begin(), tags.end(), elem.first); if (it != tags.end()) { *it = elem.second; } } } static void handle_deletions(std::vector& tags) { for (const std::string& tag : deletions) { tags.erase(std::remove(tags.begin(), tags.end(), tag), tags.end()); } } static void process(const char* path) { const char* tagname {"user.xdg.tags"}; std::vector tags; std::vector buf(1024); ssize_t sz = getxattr(path, tagname, buf.data(), buf.size()); if (sz == -1) { if (errno != ENODATA) { throw_if_min1(sz); } } else { std::string val(buf.data(), sz); tags = sc::split(val, ","); } bool edits = additions.size() > 0 || changes.size() > 0 || deletions.size() > 0; if (edits) { if (should_list) { std::cout << path << '\n'; std::cout << " before: " << sc::io::greenf << sc::join(tags, ", ") << sc::io::reset << '\n'; } handle_deletions(tags); handle_changes(tags); handle_additions(tags); if (sort) { std::sort(tags.begin(), tags.end()); } if (should_list) { std::cout << " after: " << sc::io::yellowf << sc::join(tags, ", ") << sc::io::reset << '\n'; } std::string joined {sc::join(tags, ",")}; throw_if_min1(setxattr(path, tagname, joined.c_str(), joined.size(), 0)); } else { if (should_list) { std::cout << path << ": " << sc::io::yellowf << sc::join(tags, ", ") << sc::io::reset << '\n'; } } } static void print_help() { std::cout << "usage: tagger [-h|--version]\n"; std::cout << " -h, --help show this help text and exit\n"; std::cout << " --version show version number and exit\n"; } static void print_version() { std::cout << "tagger version 1.0"; if (commit[0] != '\0') { std::cout << ", build " << commit; } std::cout << std::endl; } int main(int argc, char* argv[]) { try { int opt_char, opt_val; struct option long_options[] = { {"add", required_argument, nullptr, 'a'}, {"change", required_argument, nullptr, 'c'}, {"delete", required_argument, nullptr, 'd'}, {"help", no_argument, nullptr, 'h'}, {"into", required_argument, nullptr, 'i'}, {"list", no_argument, nullptr, 'l'}, {"sort", no_argument, nullptr, 's'}, {"version", no_argument, &opt_val, 1}, {nullptr, 0, nullptr, 0} }; while ((opt_char = getopt_long(argc, argv, "a:c:d:hi:ls", long_options, nullptr)) != -1) { std::string arg {optarg ? optarg : ""}; switch (opt_char) { case 0: { // handle long-only options here switch (opt_val) { case 1: print_version(); return EXIT_SUCCESS; } break; } case 'a': additions.push_back(arg); break; case 'c': change = arg; break; case 'd': deletions.push_back(arg); break; case 'h': print_help(); return EXIT_SUCCESS; case 'i': changes.emplace(change, arg); break; case 'l': should_list = true; break; case 's': sort = true; break; case '?': throw std::runtime_error("unrecognized option"); } } if (optind == argc) { // here when no file args throw std::runtime_error {"please specify input files"}; } for (int i = optind; i < argc; ++i) { try { process(argv[i]); } catch (const std::runtime_error& ex) { std::cerr << "tagger: " << ex.what() << '\n'; } } } catch (const std::exception& ex) { std::cerr << "tagger: " << ex.what() << '\n'; return EXIT_FAILURE; } return EXIT_SUCCESS; }