Shell: Allow * and ? wildcard expansion in arguments

Should also presumably allow for escaping and such, but this is a start.
Fixes #112.
This commit is contained in:
Robin Burchell 2019-05-26 20:36:16 +02:00 committed by Andreas Kling
parent 4040c6137d
commit 9947ee9566
3 changed files with 115 additions and 2 deletions

View file

@ -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<StringImpl> m_impl;
};

View file

@ -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;
}
}

View file

@ -12,6 +12,7 @@
#include <sys/utsname.h>
#include <AK/FileSystemPath.h>
#include <LibCore/CElapsedTimer.h>
#include <LibCore/CDirIterator.h>
#include "GlobalState.h"
#include "Parser.h"
#include "LineEditor.h"
@ -223,6 +224,47 @@ struct CommandTimer {
CElapsedTimer timer;
};
static Vector<String> process_arguments(const Vector<String>& args)
{
Vector<String> 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<String> argv_string = process_arguments(subcommand.args);
Vector<const char*> 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<char**>(argv.data()), retval))
return retval;