From 9947ee9566ac1ba6a5bb9f3ea31879154d9b42fc Mon Sep 17 00:00:00 2001 From: Robin Burchell Date: Sun, 26 May 2019 20:36:16 +0200 Subject: [PATCH] Shell: Allow * and ? wildcard expansion in arguments Should also presumably allow for escaping and such, but this is a start. Fixes #112. --- AK/AKString.h | 7 +++++++ AK/String.cpp | 54 ++++++++++++++++++++++++++++++++++++++++++++++++ Shell/main.cpp | 56 ++++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 115 insertions(+), 2 deletions(-) diff --git a/AK/AKString.h b/AK/AKString.h index 8df1bf162d..80014ac8e1 100644 --- a/AK/AKString.h +++ b/AK/AKString.h @@ -61,7 +61,13 @@ public: { } + enum class CaseSensitivity { + CaseInsensitive, + CaseSensitive, + }; + static String repeated(char, int count); + bool matches(const String& pattern, CaseSensitivity = CaseSensitivity::CaseInsensitive) const; int to_int(bool& ok) const; unsigned to_uint(bool& ok) const; @@ -136,6 +142,7 @@ public: StringView view() const { return { characters(), length() }; } private: + bool match_helper(const String& mask) const; RetainPtr m_impl; }; diff --git a/AK/String.cpp b/AK/String.cpp index 3298e833aa..8544e4c98a 100644 --- a/AK/String.cpp +++ b/AK/String.cpp @@ -196,4 +196,58 @@ String String::repeated(char ch, int count) return *impl; } +bool String::matches(const String& mask, CaseSensitivity case_sensitivity) const +{ + if (case_sensitivity == CaseSensitivity::CaseInsensitive) { + String this_lower = this->to_lowercase(); + String mask_lower = mask.to_lowercase(); + return this_lower.match_helper(mask_lower); + } + + return match_helper(mask); +} + +bool String::match_helper(const String& mask) const +{ + if (is_null() || mask.is_null()) + return false; + + const char* string_ptr = characters(); + const char* mask_ptr = mask.characters(); + + // Match string against mask directly unless we hit a * + while ((*string_ptr) && (*mask_ptr != '*')) { + if ((*mask_ptr != *string_ptr) && (*mask_ptr != '?')) + return false; + mask_ptr++; + string_ptr++; + } + + const char* cp = nullptr; + const char* mp = nullptr; + + while (*string_ptr) { + if (*mask_ptr == '*') { + // If we have only a * left, there is no way to not match. + if (!*++mask_ptr) + return true; + mp = mask_ptr; + cp = string_ptr+1; + } else if ((*mask_ptr == *string_ptr) || (*mask_ptr == '?')) { + mask_ptr++; + string_ptr++; + } else { + mask_ptr = mp; + string_ptr = cp++; + } + } + + // Handle any trailing mask + while (*mask_ptr == '*') + mask_ptr++; + + // If we 'ate' all of the mask then we match. + return !*mask_ptr; +} + } diff --git a/Shell/main.cpp b/Shell/main.cpp index 2cba5b352d..b596fc2c93 100644 --- a/Shell/main.cpp +++ b/Shell/main.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include "GlobalState.h" #include "Parser.h" #include "LineEditor.h" @@ -223,6 +224,47 @@ struct CommandTimer { CElapsedTimer timer; }; +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); + } + } + } + + return argv_string; +} + static int run_command(const String& cmd) { if (cmd.is_empty()) @@ -330,11 +372,21 @@ static int run_command(const String& cmd) for (int i = 0; i < subcommands.size(); ++i) { auto& subcommand = subcommands[i]; + Vector argv_string = process_arguments(subcommand.args); Vector argv; - for (auto& arg : subcommand.args) - argv.append(arg.characters()); + argv.ensure_capacity(argv_string.size()); + for (const auto& s : argv_string) { + argv.append(s.characters()); + } argv.append(nullptr); +#ifdef SH_DEBUG + for (auto& arg : argv) { + dbgprintf("<%s> ", arg); + } + dbgprintf("\n"); +#endif + int retval = 0; if (handle_builtin(argv.size() - 1, const_cast(argv.data()), retval)) return retval;