157 lines
3.5 KiB
Beef
157 lines
3.5 KiB
Beef
using System;
|
|
using System.IO;
|
|
using System.Collections;
|
|
using System.Diagnostics;
|
|
|
|
namespace CxxBuilder;
|
|
|
|
static class FileMatcher
|
|
{
|
|
public enum Error
|
|
{
|
|
ExpectedEscapeSequence,
|
|
CharacterClassNotClosed,
|
|
}
|
|
|
|
public static Result<void, Error> HandleMatches(Span<StringView> patterns, StringView directory, delegate void(StringView match) callback)
|
|
{
|
|
Runtime.Assert(Directory.Exists(directory), scope $"No such directory {directory}");
|
|
void Dir(StringView dir)
|
|
{
|
|
dir: for (let element in Directory.Enumerate(dir))
|
|
{
|
|
let path = element.GetFilePath(..scope .());
|
|
path.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
|
|
if (element.IsDirectory)
|
|
{
|
|
Dir(path);
|
|
continue;
|
|
}
|
|
let relpath = Path.GetRelativePath(path, directory, ..scope .());
|
|
for (let pattern in patterns)
|
|
{
|
|
if (!pattern.StartsWith('^') && !pattern.StartsWith('!')) continue;
|
|
if (IsMatch(pattern[1...], relpath))
|
|
continue dir;
|
|
}
|
|
for (let pattern in patterns)
|
|
{
|
|
if (pattern.StartsWith('^') || pattern.StartsWith('!')) continue;
|
|
if (!IsMatch(pattern, relpath)) continue;
|
|
callback(relpath);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
Dir(directory);
|
|
return .Ok;
|
|
}
|
|
|
|
public static Result<bool, Error> IsMatch(StringView pattern, StringView path)
|
|
{
|
|
var pattern, path;
|
|
|
|
Result<bool, Error> Matches(StringView pattern, char8 c)
|
|
{
|
|
if (pattern.IsEmpty) return false;
|
|
|
|
switch (pattern[0])
|
|
{
|
|
case '*':
|
|
if (pattern.Length > 1 && pattern[1] == '*')
|
|
return true;
|
|
return !Path.IsDirectorySeparatorChar(c);
|
|
case '?': return true;
|
|
case '\\':
|
|
if (pattern.Length <= 1) return .Err(.ExpectedEscapeSequence);
|
|
return c == pattern[1];
|
|
case '/': return Path.IsDirectorySeparatorChar(c);
|
|
case '[':
|
|
int i = 0;
|
|
char8 Next()
|
|
{
|
|
if (pattern.Length <= ++i)
|
|
return (.)7;
|
|
return pattern[i];
|
|
}
|
|
bool negated = false;
|
|
char8 current;
|
|
switch (Next())
|
|
{
|
|
case (.)7: return .Err(.CharacterClassNotClosed);
|
|
case '^', '!': negated = true; fallthrough;
|
|
case '\\': current = Next();
|
|
case ']': return false;
|
|
default: current = _;
|
|
}
|
|
while (true)
|
|
{
|
|
if (c == current) return !negated;
|
|
switch (Next())
|
|
{
|
|
case ']': return negated;
|
|
case '-':
|
|
let end = Next();
|
|
if (c >= current && c <= end)
|
|
return !negated;
|
|
current = Next();
|
|
case '\\': current = Next();
|
|
default: current = _;
|
|
}
|
|
}
|
|
default:
|
|
return c == _;
|
|
}
|
|
}
|
|
|
|
reduce: while (true)
|
|
{
|
|
if (path.IsEmpty) return true;
|
|
if (pattern.IsEmpty) return false;
|
|
switch (Matches(pattern, path[0]))
|
|
{
|
|
case .Err: return _;
|
|
case .Ok(false): return false;
|
|
case .Ok:
|
|
}
|
|
switch (pattern[0])
|
|
{
|
|
case '*':
|
|
StringView lazy;
|
|
if (pattern.Length > 1)
|
|
{
|
|
lazy = pattern;
|
|
lazy.RemoveFromStart(pattern[1] == '*' ? 2 : 1);
|
|
}
|
|
else lazy = .();
|
|
defer { pattern = lazy; }
|
|
while (true)
|
|
{
|
|
if (Try!(IsMatch(lazy, path))) continue reduce;
|
|
if (!Try!(Matches(pattern, path[0]))) continue reduce;
|
|
path.RemoveFromStart(1);
|
|
if (path.IsEmpty) return lazy.IsEmpty;
|
|
}
|
|
case '[':
|
|
bool escaped = false;
|
|
while (true)
|
|
{
|
|
let c = pattern[0];
|
|
pattern.RemoveFromStart(1);
|
|
if (c == ']' && !escaped)
|
|
{
|
|
path.RemoveFromStart(1);
|
|
continue reduce;
|
|
}
|
|
escaped = c == '\\';
|
|
}
|
|
case '\\':
|
|
pattern.RemoveFromStart(2);
|
|
path.RemoveFromStart(1);
|
|
default:
|
|
pattern.RemoveFromStart(1);
|
|
path.RemoveFromStart(1);
|
|
}
|
|
}
|
|
}
|
|
} |