Files
container/Sources/ContainerBuild/Globber.swift
Kathryn Baldauf 8e9670c8f8 Initial commit
Co-authored-by: Aditya Ramani <a_ramani@apple.com>
Co-authored-by: Agam Dua <agam_dua@apple.com>
Co-authored-by: Danny Canter <danny_canter@apple.com>
Co-authored-by: Dmitry Kovba <dkovba@apple.com>
Co-authored-by: Eric Ernst <eric_ernst@apple.com>
Co-authored-by: Evan Hazlett <ehazlett@apple.com>
Co-authored-by: Gilbert Song <gilbertsong@apple.com>
Co-authored-by: Hugh Bussell <hbussell@apple.com>
Co-authored-by: John Logan <john_logan@apple.com>
Co-authored-by: Kathryn Baldauf <k_baldauf@apple.com>
Co-authored-by: Madhu Venugopal <mvenugopal@apple.com>
Co-authored-by: Michael Crosby <michael_crosby@apple.com>
Co-authored-by: Sidhartha Mani <sidhartha_mani@apple.com>
Co-authored-by: Tanweer Noor <tnoor@apple.com>
Co-authored-by: Ximena Perez Diaz <xperez528@gmail.com>
Co-authored-by: Yibo Zhuang <yzhuang@apple.com>
2025-06-05 15:51:55 -07:00

122 lines
4.2 KiB
Swift

//===----------------------------------------------------------------------===//
// Copyright © 2025 Apple Inc. and the container project authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//===----------------------------------------------------------------------===//
import Foundation
public class Globber {
let input: URL
var results: Set<URL> = .init()
public init(_ input: URL) {
self.input = input
}
public func match(_ pattern: String) throws {
let adjustedPattern =
pattern
.replacingOccurrences(of: #"^\./(?=.)"#, with: "", options: .regularExpression)
.replacingOccurrences(of: "^\\.[/]?$", with: "*", options: .regularExpression)
.replacingOccurrences(of: "\\*{2,}[/]", with: "*/**/", options: .regularExpression)
.replacingOccurrences(of: "[/]\\*{2,}([^/])", with: "/**/*$1", options: .regularExpression)
.replacingOccurrences(of: "^\\*{2,}([^/])", with: "**/*$1", options: .regularExpression)
for child in input.children {
try self.match(input: child, components: adjustedPattern.split(separator: "/").map(String.init))
}
}
private func match(input: URL, components: [String]) throws {
if components.isEmpty {
var dir = input.standardizedFileURL
while dir != self.input.standardizedFileURL {
results.insert(dir)
guard dir.pathComponents.count > 1 else { break }
dir.deleteLastPathComponent()
}
return input.childrenRecursive.forEach { results.insert($0) }
}
let head = components.first ?? ""
let tail = components.tail
if head == "**" {
var tail: [String] = tail
while tail.first == "**" {
tail = tail.tail
}
try self.match(input: input, components: tail)
for child in input.children {
try self.match(input: child, components: components)
}
return
}
if try glob(input.lastPathComponent, head) {
try self.match(input: input, components: tail)
for child in input.children where try glob(child.lastPathComponent, tail.first ?? "") {
try self.match(input: child, components: tail)
}
return
}
}
func glob(_ input: String, _ pattern: String) throws -> Bool {
let regexPattern =
"^"
+ NSRegularExpression.escapedPattern(for: pattern)
.replacingOccurrences(of: "\\*", with: "[^/]*")
.replacingOccurrences(of: "\\?", with: "[^/]")
.replacingOccurrences(of: "[\\^", with: "[^")
.replacingOccurrences(of: "\\[", with: "[")
.replacingOccurrences(of: "\\]", with: "]") + "$"
// validate the regex pattern created
let _ = try Regex(regexPattern)
return input.range(of: regexPattern, options: .regularExpression) != nil
}
}
extension URL {
var children: [URL] {
(try? FileManager.default.contentsOfDirectory(at: self, includingPropertiesForKeys: nil))
?? []
}
var childrenRecursive: [URL] {
var results: [URL] = []
if let enumerator = FileManager.default.enumerator(
at: self, includingPropertiesForKeys: [.isDirectoryKey, .isSymbolicLinkKey])
{
while let child = enumerator.nextObject() as? URL {
results.append(child)
}
}
return [self] + results
}
}
extension [String] {
var tail: [String] {
if self.count <= 1 {
return []
}
return Array(self.dropFirst())
}
}