diff options
Diffstat (limited to 'sdn-mc-ext.cpp')
-rw-r--r-- | sdn-mc-ext.cpp | 222 |
1 files changed, 222 insertions, 0 deletions
diff --git a/sdn-mc-ext.cpp b/sdn-mc-ext.cpp new file mode 100644 index 0000000..9e06760 --- /dev/null +++ b/sdn-mc-ext.cpp @@ -0,0 +1,222 @@ +// +// sdn-mc-ext: Midnight Commander extension file processor +// +// Copyright (c) 2024, Přemysl Eric Janouch <p@janouch.name> +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// + +#include <cstdlib> +#include <cctype> +#include <iostream> +#include <regex> +#include <string> +#include <unordered_map> +#include <vector> + +// Trailing return types make C++ syntax suck considerably less +#define fun static auto + +using namespace std; + +// It is completely fine if this only modifies ASCII letters. +fun tolower (const string &s) -> string { + string result; + for (auto c : s) result += tolower (c); + return result; +} + +fun shell_escape (const string &v) -> string { + return "'" + regex_replace (v, regex {"'"}, "'\\''") + "'"; +} + +string arg_type, arg_path, arg_basename, arg_dirname, arg_verb; +unordered_map<string, unordered_map<string, string>> sections; + +fun expand_command (string command) -> pair<string, string> { + regex re_sequence {R"(%(%|[[:alpha:]]*\{([^}]*)\}|[[:alpha:]]+))"}; + regex re_name {R"([^{}]*)"}; + regex re_parameter {R"([^,]+")"}; + string kind, out, pipe; smatch m; + while (regex_search (command, m, re_sequence)) { + out.append (m.prefix ()); + auto seq = m.str (1); + command = m.suffix (); + + string argument = m.str (2); + if (regex_search (seq, m, re_name)) + seq = m.str (); + + if (seq == "%") { + out += "%"; + } else if (seq == "p") { + out += shell_escape (arg_basename); + } else if (seq == "f") { + out += shell_escape (arg_path); + } else if (seq == "d") { + out += shell_escape (arg_dirname); + } else if (seq == "var") { + string value; + if (auto colon = argument.find (':'); colon == argument.npos) { + if (auto v = getenv (argument.c_str ())) + value = v; + } else { + value = argument.substr (colon + 1); + if (auto v = getenv (argument.substr (0, colon).c_str ())) + value = v; + } + out += shell_escape (value); + } else if (seq == "cd") { + kind = seq; + command = regex_replace (command, regex {"^ +"}, ""); + } else if (seq == "view") { + kind = seq; + command = regex_replace (command, regex {"^ +"}, ""); + + sregex_token_iterator it (argument.begin (), argument.end (), + re_parameter, 0), end; + for (; it != end; it++) { + if (*it == "hex") + pipe.append (" | od -t x1"); + + // more(1) and less(1) either ignore or display this: + //if (*it == "nroff") + // pipe.append (" | col -b"); + } + } else if (seq == "") { + cerr << "sdn-mc-ext: prompting not supported" << endl; + return {}; + } else { + cerr << "sdn-mc-ext: unsupported: %" << seq << endl; + return {}; + } + } + return {kind, + pipe.empty () ? out.append (command) : "(" + out + ")" + pipe}; +} + +fun print_command (string cmd) { + auto command = expand_command (cmd); + cout << get<0> (command) << endl << get<1> (command) << endl; +} + +fun section_matches (const unordered_map<string, string> §ion) -> bool { + if (section.count ("Directory")) + return false; + + // The configuration went through some funky changes; + // unescape \\ but leave other escapes alone. + auto filter_re = [](const string &s) { + string result; + for (size_t i = 0; i < s.length (); ) { + auto c = s[i++]; + if (c == '\\' && i < s.length ()) + if (c = s[i++]; c != '\\') + result += '\\'; + result += c; + } + return result; + }; + auto is_true = [&](const string &name) { + auto value = section.find (name); + return value != section.end () && value->second == "true"; + }; + if (auto kv = section.find ("Type"); kv != section.end ()) { + auto flags = std::regex::ECMAScript; + if (is_true ("TypeIgnoreCase")) + flags |= regex_constants::icase; + if (!regex_search (arg_type, regex {filter_re (kv->second), flags})) + return false; + } + auto basename = arg_basename; + if (auto kv = section.find ("Regex"); kv != section.end ()) { + auto flags = std::regex::ECMAScript; + if (is_true ("RegexIgnoreCase")) + flags |= regex_constants::icase; + return regex_search (basename, regex {filter_re (kv->second), flags}); + } + if (auto kv = section.find ("Shell"); kv != section.end ()) { + auto value = kv->second; + if (is_true ("ShellIgnoreCase")) { + value = tolower (value); + basename = tolower (arg_basename); + } + if (value.empty () || value[0] != '.') + return value == basename; + return basename.length () >= value.length () && + basename.substr (basename.length () - value.length ()) == value; + } + return !arg_type.empty (); +} + +fun process (const string §ion) -> bool { + auto full = sections.at (section); + if (auto include = full.find ("Include"); include != full.end ()) { + full.erase ("Open"); + full.erase ("View"); + full.erase ("Edit"); + + if (auto included = sections.find ("Include/" + include->second); + included != sections.end ()) { + for (const auto &kv : included->second) + full[kv.first] = kv.second; + } + } + if (getenv ("SDN_MC_EXT_DEBUG")) { + cerr << "[" << section << "]" << endl; + for (const auto &kv : full) + cerr << " " << kv.first << ": " << kv.second << endl; + } + if (full.count (arg_verb) && section_matches (full)) { + print_command (full[arg_verb]); + return true; + } + return false; +} + +int main (int argc, char *argv[]) { + if (argc != 6) { + cerr << "Usage: " << argv[0] + << " TYPE PATH BASENAME DIRNAME VERB < mc.ext.ini" << endl; + return 2; + } + + arg_type = argv[1]; + arg_path = argv[2], arg_basename = argv[3], arg_dirname = argv[4]; + arg_verb = argv[5]; + + string line, section; + vector<string> order; + regex re_entry {R"(^([-\w]+) *= *(.*)$)"}; + smatch m; + while (getline (cin, line)) { + if (line.empty () || line[0] == '#') { + continue; + } else if (auto length = line.length(); + line.find_last_of ('[') == 0 && + line.find_first_of (']') == length - 1) { + order.push_back ((section = line.substr (1, length - 2))); + } else if (regex_match (line, m, re_entry)) { + sections[section][m[1]] = m[2]; + } + } + for (const auto §ion : order) { + if (section == "mc.ext.ini" || + section == "Default" || + section.substr (0, 8) == "Include/") + continue; + if (process (section)) + return 0; + } + print_command (sections["Default"][arg_verb]); + return 0; +} |