1
0
mirror of https://github.com/nicoverbruggen/phpmon.git synced 2025-08-08 04:20:07 +02:00
Files
app/phpmon/Common/Testables/TestableFileSystem.swift
2023-01-19 18:09:42 +01:00

289 lines
7.3 KiB
Swift

//
// TestableFileSystem.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 04/10/2022.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
//
import Foundation
class TestableFileSystem: FileSystemProtocol {
/**
Initialize a fake filesystem with a bunch of files.
You do not need to specify directories (unless symlinks), those will be created automatically.
*/
init(files: [String: FakeFile]) {
self.files = files
// Ensure that each of the ~ characters are replaced with the home directory path
for key in self.files.keys where key.contains("~") {
self.files.renameKey(
fromKey: key,
toKey: key.replacingOccurrences(of: "~", with: self.homeDirectory)
)
}
// Ensure that intermediate directories are created
for file in self.files {
self.createIntermediateDirectories(file.key)
}
}
/**
Internal file handling of the fake filesystem.
You can easily dump what's in here by using:
```
let fs = FileSystem as! TestableFileSystem
fs.printContents()
```
*/
private(set) var files: [String: FakeFile]
/**
The home directory for the fake filesystem.
*/
private(set) var homeDirectory = "/Users/fake"
// MARK: - Basics
func createDirectory(_ path: String, withIntermediateDirectories: Bool) throws {
let path = path.replacingTildeWithHomeDirectory
if files[path] != nil {
throw TestableFileSystemError.alreadyExists
}
self.createIntermediateDirectories(path)
self.files[path] = .fake(.directory)
}
func writeAtomicallyToFile(_ path: String, content: String) throws {
let path = path.replacingTildeWithHomeDirectory
if files[path] != nil {
throw TestableFileSystemError.alreadyExists
}
self.files[path] = .fake(.text, content)
}
func getStringFromFile(_ path: String) throws -> String {
let path = path.replacingTildeWithHomeDirectory
guard let file = files[path] else {
throw TestableFileSystemError.fileMissing
}
return file.content ?? ""
}
func getShallowContentsOfDirectory(_ path: String) throws -> [String] {
let path = path.replacingTildeWithHomeDirectory
var seek = path
if !seek.hasSuffix("/") {
seek = "\(seek)/"
}
return self.files.keys
.filter { $0.hasPrefix(seek) }
.map { $0.replacingOccurrences(of: seek, with: "") }
.filter { !$0.contains("/") }
}
func getDestinationOfSymlink(_ path: String) throws -> String {
let path = path.replacingTildeWithHomeDirectory
guard let file = files[path] else {
throw TestableFileSystemError.fileMissing
}
if file.type != .symlink {
throw TestableFileSystemError.notSymlink
}
guard let pathToSymlink = file.content else {
throw TestableFileSystemError.invalidSymlink
}
if !files.keys.contains(pathToSymlink) {
throw TestableFileSystemError.invalidSymlink
}
return pathToSymlink
}
// MARK: - Move & Delete Files
func move(from path: String, to newPath: String) throws {
let path = path.replacingTildeWithHomeDirectory
let newPath = newPath.replacingTildeWithHomeDirectory
self.files.keys.forEach { key in
if key.hasPrefix(path) {
self.files.renameKey(
fromKey: key,
toKey: key.replacingOccurrences(of: path, with: newPath)
)
}
}
self.files.renameKey(fromKey: path, toKey: newPath)
}
func remove(_ path: String) throws {
// Remove recursively
self.files.keys.forEach { key in
if key.hasPrefix(path) {
self.files.removeValue(forKey: key)
}
}
self.files.removeValue(forKey: path)
}
// MARK: Attributes
func makeExecutable(_ path: String) throws {
let path = path.replacingTildeWithHomeDirectory
guard let file = files[path] else {
throw TestableFileSystemError.fileMissing
}
file.type = .binary
}
// MARK: - Checks
func isExecutableFile(_ path: String) -> Bool {
let path = path.replacingTildeWithHomeDirectory
guard let file = files[path.replacingTildeWithHomeDirectory] else {
return false
}
return file.type == .binary
}
func isWriteableFile(_ path: String) -> Bool {
let path = path.replacingTildeWithHomeDirectory
guard let file = files[path.replacingTildeWithHomeDirectory] else {
return false
}
return !file.readOnly
}
func anyExists(_ path: String) -> Bool {
let path = path.replacingTildeWithHomeDirectory
return files.keys.contains(path)
}
func fileExists(_ path: String) -> Bool {
let path = path.replacingTildeWithHomeDirectory
guard let file = files[path] else {
return false
}
return [.binary, .symlink, .text].contains(file.type)
}
func directoryExists(_ path: String) -> Bool {
let path = path.replacingTildeWithHomeDirectory
guard let file = files[path] else {
return false
}
return [.directory].contains(file.type)
}
func isSymlink(_ path: String) -> Bool {
let path = path.replacingTildeWithHomeDirectory
guard let file = files[path] else {
return false
}
return file.type == .symlink
}
func isDirectory(_ path: String) -> Bool {
let path = path.replacingTildeWithHomeDirectory
guard let file = files[path] else {
return false
}
return file.type == .directory
}
public func printContents() {
for key in self.files.keys.sorted() {
print("\(key) -> \(self.files[key]!.type)")
}
}
private func createIntermediateDirectories(_ path: String) {
let path = path.replacingTildeWithHomeDirectory
let items = path.components(separatedBy: "/")
var preceding = ""
for item in items {
let key = preceding == "/"
? "/\(item)"
: "\(preceding)/\(item)"
if !self.files.keys.contains(key) {
self.files[key] = .fake(.directory)
}
preceding = key
}
}
}
enum FakeFileType: Codable {
case binary, text, directory, symlink
}
class FakeFile: Codable {
var type: FakeFileType
var content: String?
var readOnly: Bool = false
init(type: FakeFileType, content: String?, readOnly: Bool = false) {
self.type = type
self.content = content
self.readOnly = readOnly
}
public static func fake(
_ type: FakeFileType,
_ content: String? = nil,
readOnly: Bool = false
) -> FakeFile {
return FakeFile(
type: type,
content: content,
readOnly: readOnly
)
}
}
enum TestableFileSystemError: Error {
case fileMissing
case alreadyExists
case notSymlink
case invalidSymlink
}