225 lines
4.4 KiB
Swift
225 lines
4.4 KiB
Swift
//
|
|
// Cask.swift
|
|
// Brewer
|
|
//
|
|
// Created by Cédric MAS on 08/07/2024.
|
|
//
|
|
|
|
import Foundation
|
|
|
|
enum ShellError: Error {
|
|
case emptyOutput
|
|
}
|
|
|
|
@discardableResult
|
|
func shell(_ command: String) throws -> String {
|
|
let task = Process()
|
|
task.executableURL = URL(filePath: "/bin/bash")
|
|
task.arguments = ["-c", command]
|
|
|
|
let pipe = Pipe()
|
|
task.standardError = pipe
|
|
task.standardOutput = pipe
|
|
|
|
try task.run()
|
|
if let data = try pipe.fileHandleForReading.readToEnd(),
|
|
let output = String(data: data, encoding: .utf8)
|
|
{
|
|
return output
|
|
}
|
|
|
|
throw ShellError.emptyOutput
|
|
}
|
|
|
|
struct Cask: Codable {
|
|
let token: String
|
|
let fullToken: String
|
|
let tap: String
|
|
let name: [String]
|
|
let desc: String
|
|
let homepage: String
|
|
let url: String
|
|
let version: String
|
|
let installed: String?
|
|
let outdated: Bool
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case token, tap, name, desc, homepage, url, version, installed, outdated
|
|
case fullToken = "full_token"
|
|
}
|
|
}
|
|
|
|
struct Formulae: Codable {
|
|
|
|
struct Version: Codable {
|
|
let stable: String
|
|
let head: String?
|
|
let bottle: Bool
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case stable
|
|
case head, bottle
|
|
}
|
|
}
|
|
|
|
let name: String
|
|
let fullName: String
|
|
let tap: String
|
|
let oldNames: [String]
|
|
let aliases: [String]
|
|
let versionedFormulae: [String]
|
|
let desc: String
|
|
let license: String?
|
|
let homepage: String
|
|
let versions: Version
|
|
let outdated: Bool
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case name
|
|
case tap
|
|
case aliases
|
|
case desc
|
|
case license
|
|
case homepage
|
|
case versions
|
|
case outdated
|
|
case fullName = "full_name"
|
|
case oldNames = "oldnames"
|
|
case versionedFormulae = "versioned_formulae"
|
|
}
|
|
}
|
|
|
|
struct InfoResponse: Codable {
|
|
let formulae: [Formulae]
|
|
let casks: [Cask]
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case casks
|
|
case formulae
|
|
}
|
|
}
|
|
|
|
@Observable
|
|
class Homebrew {
|
|
var data: InfoResponse?
|
|
var isLoading = false
|
|
var errorMessage: String?
|
|
|
|
func getInfo(on query: String) {
|
|
self.isLoading = true
|
|
self.errorMessage = nil
|
|
self.data = nil
|
|
Task { [weak self] in
|
|
do {
|
|
let res = try shell(
|
|
"/opt/homebrew/bin/brew info --json=v2 \(query)")
|
|
if let data = res.data(using: .utf8) {
|
|
let output = try JSONDecoder().decode(
|
|
InfoResponse.self, from: data)
|
|
self?.data = output
|
|
}
|
|
} catch {
|
|
self?.errorMessage = error.localizedDescription
|
|
}
|
|
self?.isLoading = false
|
|
}
|
|
}
|
|
|
|
func getInstalled() {
|
|
self.isLoading = true
|
|
self.data = nil
|
|
self.errorMessage = nil
|
|
Task { [weak self] in
|
|
do {
|
|
let res = try shell(
|
|
"/opt/homebrew/bin/brew info --json=v2 --installed")
|
|
if let data = res.data(using: .utf8) {
|
|
let output = try JSONDecoder().decode(
|
|
InfoResponse.self, from: data)
|
|
self?.data = output
|
|
}
|
|
} catch {
|
|
self?.errorMessage = error.localizedDescription
|
|
}
|
|
self?.isLoading = false
|
|
}
|
|
|
|
}
|
|
|
|
func isDownloaded(_ name: String) async -> Bool {
|
|
self.isLoading = true
|
|
self.errorMessage = nil
|
|
self.data = nil
|
|
let task = Task { [weak self] in
|
|
do {
|
|
let res = try shell(
|
|
"/opt/homebrew/bin/brew list -1 \(name) >/dev/null 2>&1; echo $?"
|
|
)
|
|
.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
self?.isLoading = false
|
|
return res == "0"
|
|
} catch {
|
|
self?.errorMessage = error.localizedDescription
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
switch await task.result {
|
|
case .success(let success):
|
|
return success
|
|
case .failure(let fail):
|
|
return false
|
|
}
|
|
}
|
|
|
|
func install(_ fullToken: String, isCask: Bool = false) async -> Bool {
|
|
self.isLoading = true
|
|
self.data = nil
|
|
let task = Task { [weak self] in
|
|
do {
|
|
try shell(
|
|
"/opt/homebrew/bin/brew install \(isCask ? "--cask" : "") \(fullToken)"
|
|
)
|
|
} catch {
|
|
self?.errorMessage = error.localizedDescription
|
|
}
|
|
self?.isLoading = false
|
|
return await self?.isDownloaded(fullToken) ?? false
|
|
}
|
|
|
|
return switch await task.result {
|
|
case .success(let success):
|
|
success
|
|
case .failure(let fail):
|
|
false
|
|
}
|
|
}
|
|
|
|
func uninstall(_ fullToken: String) async -> Bool {
|
|
self.isLoading = true
|
|
self.data = nil
|
|
let task = Task { [weak self] in
|
|
do {
|
|
let res =
|
|
try shell(
|
|
"/opt/homebrew/bin/brew uninstall \(fullToken); echo $?"
|
|
)
|
|
.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
} catch {
|
|
self?.errorMessage = error.localizedDescription
|
|
}
|
|
self?.isLoading = false
|
|
return await !(self?.isDownloaded(fullToken) ?? true)
|
|
}
|
|
|
|
return switch await task.result {
|
|
case .success(let success):
|
|
success
|
|
case .failure(let failure):
|
|
false
|
|
}
|
|
|
|
}
|
|
}
|