From 802612f665a2d15f22840b7716522d6ad786247a Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Thu, 13 Jun 2019 16:38:18 +0300 Subject: [PATCH] Shell: Implement more advanced globbing. A glob has to be resolved against the directory corresponding to the part of the path it is found in, not the current directory. For example, in /usr/i*/AK/, the glob has to be resolved inside /usr. Moreover, an argument can contain more than one glob, such as /u*/*/?, in which case they have to be resolved recursively. In case a glob matches nothing, the argument should be used as is. --- Shell/main.cpp | 131 +++++++++++++++++++++++++++++++++++++------------ 1 file changed, 99 insertions(+), 32 deletions(-) diff --git a/Shell/main.cpp b/Shell/main.cpp index 4e3102051e..a2f8080165 100644 --- a/Shell/main.cpp +++ b/Shell/main.cpp @@ -2,6 +2,7 @@ #include "LineEditor.h" #include "Parser.h" #include +#include #include #include #include @@ -219,42 +220,108 @@ struct CommandTimer { CElapsedTimer timer; }; +static bool is_glob(const StringView& s) +{ + for (int i = 0; i < s.length(); i++) { + char c = s.characters()[i]; + if (c == '*' || c == '?') + return true; + } + return false; +} + +static Vector split_path(const StringView &path) +{ + Vector parts; + + ssize_t substart = 0; + for (ssize_t i = 0; i < path.length(); i++) { + char ch = path.characters()[i]; + if (ch != '/') + continue; + ssize_t sublen = i - substart; + if (sublen != 0) + parts.append(path.substring_view(substart, sublen)); + parts.append(path.substring_view(i, 1)); + substart = i + 1; + } + + ssize_t taillen = path.length() - substart; + if (taillen != 0) + parts.append(path.substring_view(substart, taillen)); + + return parts; +} + +static Vector expand_globs(const StringView& path, const StringView& base) +{ + auto parts = split_path(path); + + StringBuilder builder; + builder.append(base); + Vector res; + + for (int i = 0; i < parts.size(); ++i) { + auto& part = parts[i]; + if (!is_glob(part)) { + builder.append(part); + continue; + } + + // Found a glob. + String new_base = builder.to_string(); + StringView new_base_v = new_base; + if (new_base_v.is_empty()) + new_base_v = "."; + CDirIterator di(new_base_v, CDirIterator::NoFlags); + + if (di.has_error()) { + return res; + } + + while (di.has_next()) { + String name = di.next_path(); + + // Dotfiles have to be explicitly requested + if (name[0] == '.' && part[0] != '.') + continue; + + // And even if they are, skip . and .. + if (name == "." || name == "..") + continue; + + if (name.matches(part, String::CaseSensitivity::CaseSensitive)) { + + StringBuilder nested_base; + nested_base.append(new_base); + nested_base.append(name); + + StringView remaining_path = path.substring_view_starting_after_substring(part); + Vector nested_res = expand_globs(remaining_path, nested_base.to_string()); + for (auto& s : nested_res) + res.append(s); + } + } + return res; + } + + // Found no globs. + String new_path = builder.to_string(); + if (access(new_path.characters(), F_OK) == 0) + res.append(new_path); + return res; +} + static Vector process_arguments(const Vector& args) { Vector argv_string; for (auto& arg : args) { - bool is_glob = false; - for (int i = 0; i < arg.length(); i++) { - char c = arg.characters()[i]; - if (c == '*' || c == '?') { - is_glob = true; - } - } - - if (is_glob == false) { - argv_string.append(arg.characters()); - } else { - CDirIterator di(".", CDirIterator::NoFlags); - if (di.has_error()) { - fprintf(stderr, "CDirIterator: %s\n", di.error_string()); - continue; - } - - while (di.has_next()) { - String name = di.next_path(); - - // Dotfiles have to be explicitly requested - if (name[0] == '.' && arg[0] != '.') - continue; - - // And even if they are, skip . and .. - if (name == "." || name == "..") - continue; - - if (name.matches(arg, String::CaseSensitivity::CaseSensitive)) - argv_string.append(name); - } - } + auto expanded = expand_globs(arg, ""); + if (expanded.is_empty()) + argv_string.append(arg); + else + for (auto& path : expand_globs(arg, "")) + argv_string.append(path); } return argv_string;