Rewrite sdn-view AWK core in C++
sdn-view is still slower than I'd like it to be, just no longer ridiculously so.
This commit is contained in:
parent
3075d47aeb
commit
b070df6010
@ -26,9 +26,14 @@ target_compile_features (${PROJECT_NAME} PUBLIC cxx_std_14)
|
|||||||
target_compile_definitions (${PROJECT_NAME} PUBLIC
|
target_compile_definitions (${PROJECT_NAME} PUBLIC
|
||||||
-DPROJECT_NAME=\"${PROJECT_NAME}\" -DPROJECT_VERSION=\"${PROJECT_VERSION}\")
|
-DPROJECT_NAME=\"${PROJECT_NAME}\" -DPROJECT_VERSION=\"${PROJECT_VERSION}\")
|
||||||
|
|
||||||
|
add_executable (${PROJECT_NAME}-mc-ext ${PROJECT_NAME}-mc-ext.cpp)
|
||||||
|
target_compile_features (${PROJECT_NAME}-mc-ext PUBLIC cxx_std_17)
|
||||||
|
|
||||||
include (GNUInstallDirs)
|
include (GNUInstallDirs)
|
||||||
install (TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR})
|
# sdn-mc-ext should be in libexec, but we prefer it in PATH.
|
||||||
install (PROGRAMS ${PROJECT_NAME}-install ${PROJECT_NAME}-view
|
install (TARGETS sdn sdn-mc-ext
|
||||||
|
DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||||
|
install (PROGRAMS sdn-install sdn-view
|
||||||
DESTINATION ${CMAKE_INSTALL_BINDIR})
|
DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||||
install (FILES sdn.1 sdn-install.1 sdn-view.1
|
install (FILES sdn.1 sdn-install.1 sdn-view.1
|
||||||
DESTINATION ${CMAKE_INSTALL_MANDIR}/man1)
|
DESTINATION ${CMAKE_INSTALL_MANDIR}/man1)
|
||||||
|
@ -25,7 +25,7 @@ or as a https://git.janouch.name/p/nixexprs[Nix derivation].
|
|||||||
|
|
||||||
Building
|
Building
|
||||||
--------
|
--------
|
||||||
Build dependencies: CMake and/or make, a C++14 compiler, pkg-config +
|
Build dependencies: CMake and/or make, a C++17 compiler, pkg-config +
|
||||||
Runtime dependencies: ncursesw, libacl (on Linux)
|
Runtime dependencies: ncursesw, libacl (on Linux)
|
||||||
|
|
||||||
// Working around libasciidoc's missing support for escaping it like \++
|
// Working around libasciidoc's missing support for escaping it like \++
|
||||||
|
222
sdn-mc-ext.cpp
Normal file
222
sdn-mc-ext.cpp
Normal file
@ -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;
|
||||||
|
}
|
209
sdn-view
209
sdn-view
@ -14,198 +14,41 @@ if command -v mc >/dev/null
|
|||||||
then datadir=$(mc --datadir | sed 's/ (.*)$//')
|
then datadir=$(mc --datadir | sed 's/ (.*)$//')
|
||||||
fi
|
fi
|
||||||
|
|
||||||
export SDN_VIEW_CONFIG=
|
config=
|
||||||
for dir in "$HOME"/.config/mc "$datadir" /etc/mc
|
for dir in "$HOME"/.config/mc "$datadir" /etc/mc
|
||||||
do
|
do
|
||||||
if [ -n "$dir" -a -f "$dir/mc.ext.ini" ]
|
if [ -n "$dir" -a -f "$dir/mc.ext.ini" ]
|
||||||
then
|
then
|
||||||
SDN_VIEW_CONFIG=$dir/mc.ext.ini
|
config=$dir/mc.ext.ini
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# This is often used in %env{} expansion, so let's be on the same page.
|
||||||
export PAGER=${PAGER:-less}
|
export PAGER=${PAGER:-less}
|
||||||
|
|
||||||
export MC_EXT_FILENAME=$(realpath "$1")
|
export MC_EXT_FILENAME=$(realpath "$1")
|
||||||
export MC_EXT_BASENAME=$(basename "$1")
|
export MC_EXT_BASENAME=$(basename "$1")
|
||||||
export MC_EXT_CURRENTDIR=$(dirname "$1")
|
export MC_EXT_CURRENTDIR=$(dirname "$MC_EXT_FILENAME")
|
||||||
export SDN_VIEW_TYPE=$(file -bz "$1")
|
output=$(sdn-mc-ext <"$config" "$(file -Lbz "$1")" \
|
||||||
process() (awk -f - <<'EOF'
|
"$MC_EXT_FILENAME" "$MC_EXT_BASENAME" "$MC_EXT_CURRENTDIR" View || :)
|
||||||
BEGIN {
|
kind=$(echo "$output" | sed -n 1p)
|
||||||
if (!(Config = ENVIRON["SDN_VIEW_CONFIG"]))
|
command=$(echo "$output" | sed -n 2p)
|
||||||
exit
|
|
||||||
|
|
||||||
Verb = "View"
|
case "$kind" in
|
||||||
Section = ""
|
view)
|
||||||
while ((getline < Config) > 0) {
|
if [ -n "$command" ]
|
||||||
if (/^\s*(#.*)?$/) {
|
then eval "$command" | "$PAGER"
|
||||||
# Skip.
|
else "$PAGER" -- "$MC_EXT_FILENAME"
|
||||||
} else if (/^\[[^]]+\]$/) {
|
fi
|
||||||
Sections[++SectionsLen] = Section = substr($0, 2, length($0) - 2)
|
;;
|
||||||
} else if (/^[^=]+=[^=]*$/) {
|
'')
|
||||||
split($0, kv, "=")
|
if [ -n "$command" ]
|
||||||
Keys[Section, kv[1]] = kv[2]
|
then eval "$command"
|
||||||
}
|
else "$PAGER" -- "$MC_EXT_FILENAME"
|
||||||
}
|
fi
|
||||||
|
;;
|
||||||
Type = ENVIRON["SDN_VIEW_TYPE"]
|
*)
|
||||||
Path = ENVIRON["MC_EXT_FILENAME"]
|
echo "Unsupported: $kind" >&2
|
||||||
Basename = ENVIRON["MC_EXT_BASENAME"]
|
exit 1
|
||||||
Dirname = ENVIRON["MC_EXT_CURRENTDIR"]
|
esac
|
||||||
|
|
||||||
for (i = 1; i <= SectionsLen; i++) {
|
|
||||||
if (Sections[i] == "mc.ext.ini" ||
|
|
||||||
Sections[i] == "Default" ||
|
|
||||||
Sections[i] ~ /^Include[\/]/)
|
|
||||||
continue
|
|
||||||
try(Sections[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
# Not attempting any inclusions here.
|
|
||||||
print expand_command(Keys["Default", Verb])
|
|
||||||
}
|
|
||||||
|
|
||||||
function try(section, pair, a, key, full, include) {
|
|
||||||
for (pair in Keys) {
|
|
||||||
split(pair, a, SUBSEP)
|
|
||||||
if (a[1] == section)
|
|
||||||
full[a[2]] = Keys[pair]
|
|
||||||
}
|
|
||||||
if ("Include" in full) {
|
|
||||||
delete full["Open"]
|
|
||||||
delete full["View"]
|
|
||||||
delete full["Edit"]
|
|
||||||
include = "Include/" full["Include"]
|
|
||||||
for (pair in Keys) {
|
|
||||||
split(pair, a, SUBSEP)
|
|
||||||
if (a[1] == include)
|
|
||||||
full[a[2]] = Keys[pair]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ENVIRON["SDN_VIEW_DEBUG"]) {
|
|
||||||
print "[" section "]" > "/dev/stderr"
|
|
||||||
for (key in full)
|
|
||||||
print " " key ": " full[key] > "/dev/stderr"
|
|
||||||
}
|
|
||||||
if (Verb in full && section_matches(full, Type, Basename)) {
|
|
||||||
print expand_command(full[Verb])
|
|
||||||
exit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function shell_escape(string) {
|
|
||||||
gsub(/'/, "'\\''", string)
|
|
||||||
return "'" string "'"
|
|
||||||
}
|
|
||||||
|
|
||||||
function expand_command(cmd, toview, out, seq, argument, value, a, pipe) {
|
|
||||||
out = ""
|
|
||||||
while (match(cmd, /%[a-zA-Z]*\{[^}]*\}|%[a-zA-Z]+|%%/)) {
|
|
||||||
out = out substr(cmd, 1, RSTART - 1)
|
|
||||||
seq = substr(cmd, RSTART + 1, RLENGTH - 1)
|
|
||||||
cmd = substr(cmd, RSTART + RLENGTH)
|
|
||||||
|
|
||||||
argument = ""
|
|
||||||
if (match(seq, /\{.*\}$/)) {
|
|
||||||
argument = substr(seq, RSTART + 1, RLENGTH - 2)
|
|
||||||
seq = substr(seq, 1, RSTART - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (seq == "%") {
|
|
||||||
out = out "%"
|
|
||||||
} else if (seq == "p") {
|
|
||||||
out = out shell_escape(Basename)
|
|
||||||
} else if (seq == "f") {
|
|
||||||
out = out shell_escape(Path)
|
|
||||||
} else if (seq == "d") {
|
|
||||||
out = out shell_escape(Dirname)
|
|
||||||
} else if (seq == "view") {
|
|
||||||
toview = 1
|
|
||||||
|
|
||||||
sub(/^ +/, "", cmd)
|
|
||||||
split(argument, a, /,/)
|
|
||||||
for (value in a) {
|
|
||||||
if (a[value] == "hex")
|
|
||||||
pipe = pipe " | od -t x1"
|
|
||||||
|
|
||||||
# more(1) and less(1) either ignore or display this:
|
|
||||||
#if (a[value] == "nroff")
|
|
||||||
# pipe = pipe " | col -b"
|
|
||||||
}
|
|
||||||
} else if (seq == "var") {
|
|
||||||
value = ""
|
|
||||||
if (!match(argument, /:.*/)) {
|
|
||||||
if (argument in ENVIRON)
|
|
||||||
value = ENVIRON[argument]
|
|
||||||
} else {
|
|
||||||
value = substr(argument, RSTART + 1)
|
|
||||||
argument = substr(argument, 1, RSTART - 1)
|
|
||||||
if (argument in ENVIRON)
|
|
||||||
value = ENVIRON[argument]
|
|
||||||
}
|
|
||||||
out = out shell_escape(value)
|
|
||||||
} else if (seq == "") {
|
|
||||||
print Config ": prompting not supported" > "/dev/stderr"
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
print Config ": unsupported: %" seq > "/dev/stderr"
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out = out cmd pipe
|
|
||||||
|
|
||||||
# While the processing is mostly generic for all verbs,
|
|
||||||
# we'd have to distinguish non-view commands in this AWK script's output.
|
|
||||||
if (!toview)
|
|
||||||
return
|
|
||||||
|
|
||||||
# In the case of out == "", we should just explicitly pass it to the pager,
|
|
||||||
# however it currently mixes with the case of "we can't use this View=".
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
function section_matches(section, type, basename, value) {
|
|
||||||
if ("Directory" in section)
|
|
||||||
return 0
|
|
||||||
|
|
||||||
if ("Type" in section) {
|
|
||||||
value = section["Type"]
|
|
||||||
if ("TypeIgnoreCase" in section &&
|
|
||||||
section["TypeIgnoreCase"] == "true") {
|
|
||||||
type = tolower(type)
|
|
||||||
value = tolower(value)
|
|
||||||
}
|
|
||||||
gsub(/\\\\/, "\\", value)
|
|
||||||
gsub(/\\ /, " ", value)
|
|
||||||
if (type !~ value)
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
if ("Regex" in section) {
|
|
||||||
value = section["Regex"]
|
|
||||||
if ("RegexIgnoreCase" in section &&
|
|
||||||
section["RegexIgnoreCase"] == "true") {
|
|
||||||
basename = tolower(basename)
|
|
||||||
value = tolower(value)
|
|
||||||
}
|
|
||||||
gsub(/\\\\/, "\\", value)
|
|
||||||
return basename ~ value
|
|
||||||
} else if ("Shell" in section) {
|
|
||||||
value = section["Shell"]
|
|
||||||
if ("RegexIgnoreCase" in section &&
|
|
||||||
section["ShellIgnoreCase"] == "true") {
|
|
||||||
basename = tolower(basename)
|
|
||||||
value = tolower(value)
|
|
||||||
}
|
|
||||||
if (value !~ /^[.]/)
|
|
||||||
return value == basename
|
|
||||||
return length(basename) >= length(value) &&
|
|
||||||
substr(basename, length(basename) - length(value) + 1) == value
|
|
||||||
}
|
|
||||||
return type != ""
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
)
|
|
||||||
command=$(process)
|
|
||||||
if [ -z "$command" ]
|
|
||||||
then "$PAGER" -- "$MC_EXT_FILENAME"
|
|
||||||
else eval "$command" | "$PAGER"
|
|
||||||
fi
|
|
||||||
|
Loading…
Reference in New Issue
Block a user