cleanup Block

This commit is contained in:
2026-07-12 00:38:52 +02:00
parent 289cc6eb61
commit 34fc5a4c0d
4 changed files with 111 additions and 117 deletions
@@ -0,0 +1,52 @@
import Foundation
import TOML
extension PHBlock {
private struct Wrapper: Decodable {
let blocks: [PHBlock]
private enum CodingKeys: String, CodingKey {
case blocks = "block"
}
}
nonisolated static var configFile: URL {
FileManager.default.homeDirectoryForCurrentUser.appending(path: ".config/phbar/blocks.toml")
}
/// Load blocks from the default path (~/.config/phbar/blocks.toml).
/// If the file doesn't exist or fails to parse, defaults are used (non-fatal).
static func load() throws -> [PHBlock] {
if FileManager.default.fileExists(atPath: Self.configFile.relativePath) {
return try load(from: Self.configFile)
} else {
throw phbar.Error("Failed to load blocks file")
}
}
/// Load theme from a file at URL.
/// If the file doesn't exist or fails to parse, the execution is interrupted.
static func load(from url: URL) throws -> [PHBlock] {
do {
let data = try Data(contentsOf: url)
guard let contents = String(data: data, encoding: .utf8), !contents.isEmpty else {
throw phbar.Error("the file is empty")
}
return try load(from: contents)
} catch {
throw phbar.Error("Failed to load blocks file", underlyingError: error)
}
}
/// Load theme from a TOML string.
/// If the content fails to parse, the execution is interrupted.
static func load(from contents: String) throws -> [PHBlock] {
do {
let decoder = TOMLDecoder()
let wrapper = try decoder.decode(PHBlock.Wrapper.self, from: contents)
return wrapper.blocks
} catch {
throw phbar.Error("Failed to parse blocks file", underlyingError: error)
}
}
}
@@ -1,69 +1,4 @@
import ArgumentParser
import Foundation import Foundation
import SwiftUI
import TOML
@MainActor
final class PHBlock: ObservableObject, Decodable, Identifiable {
let id = UUID()
let command: String
let name: String?
let styleName: String?
@Published var style: PHThemeStyle = .default
let refresh: Double?
let centered: Bool?
var debug: Bool = false
var visible: Bool {
label != nil && !label!.isEmpty
}
/// System events that should trigger a refresh (e.g. `["volume", "network"]`).
/// `nil` when omitted from config.
let events: [PHEvent]?
/// Event source registry used to subscribe to system events. Defaults to the
/// shared instance; inject a custom one for testing.
var registry: PHEventRegistry = .shared
@Published private(set) var label: String?
/// The repeating interval task, if any.
private var intervalTask: Task<Void, Never>?
/// Active event subscriptions, torn down in `stopAutoRefresh`.
private var subscriptions: [PHEventSubscription] = []
/// Guards against stacking concurrent updates during rapid event bursts
/// (e.g. dragging the volume slider fires many events in quick succession).
private var updateScheduled = false
private enum CodingKeys: String, CodingKey {
case command, name
case styleName = "style"
case refresh, events, centered
}
deinit {
intervalTask?.cancel()
subscriptions.forEach { $0.cancel() }
}
}
// Kind
extension PHBlock {
enum Kind: String {
case text, space
}
var kind: Kind {
if command.starts(with: "_space") { return .space }
return .text
}
}
// Refresh
extension PHBlock { extension PHBlock {
/// Start keeping the label up to date. /// Start keeping the label up to date.
@@ -187,55 +122,3 @@ extension PHBlock {
return lines.last?.description return lines.last?.description
} }
} }
// Loading
extension PHBlock {
private struct Wrapper: Decodable {
let blocks: [PHBlock]
enum CodingKeys: String, CodingKey {
case blocks = "block"
}
}
private nonisolated static var configFile: URL {
FileManager.default.homeDirectoryForCurrentUser.appending(path: ".config/phbar/blocks.toml")
}
/// Load blocks from the default path (~/.config/phbar/blocks.toml).
/// If the file doesn't exist or fails to parse, defaults are used (non-fatal).
static func load() throws -> [PHBlock] {
if FileManager.default.fileExists(atPath: Self.configFile.relativePath) {
return try load(from: Self.configFile)
} else {
throw phbar.Error("Failed to load blocks file")
}
}
/// Load theme from a file at URL.
/// If the file doesn't exist or fails to parse, the execution is interrupted.
static func load(from url: URL) throws -> [PHBlock] {
do {
let data = try Data(contentsOf: url)
guard let contents = String(data: data, encoding: .utf8), !contents.isEmpty else {
throw phbar.Error("the file is empty")
}
return try load(from: contents)
} catch {
throw phbar.Error("Failed to load blocks file", underlyingError: error)
}
}
/// Load theme from a TOML string.
/// If the content fails to parse, the execution is interrupted.
static func load(from contents: String) throws -> [PHBlock] {
do {
let decoder = TOMLDecoder()
let wrapper = try decoder.decode(PHBlock.Wrapper.self, from: contents)
return wrapper.blocks
} catch {
throw phbar.Error("Failed to parse blocks file", underlyingError: error)
}
}
}
+49
View File
@@ -0,0 +1,49 @@
import Foundation
@MainActor
final class PHBlock: ObservableObject, Decodable, Identifiable {
let id = UUID()
let command: String
let name: String?
let styleName: String?
@Published var style: PHThemeStyle = .default
let refresh: Double?
let centered: Bool?
var debug: Bool = false
var visible: Bool {
label != nil && !label!.isEmpty
}
/// System events that should trigger a refresh (e.g. `["volume", "network"]`).
/// `nil` when omitted from config.
let events: [PHEvent]?
/// Event source registry used to subscribe to system events. Defaults to the
/// shared instance; inject a custom one for testing.
var registry: PHEventRegistry = .shared
@Published var label: String?
/// The repeating interval task, if any.
var intervalTask: Task<Void, Never>?
/// Active event subscriptions, torn down in `stopAutoRefresh`.
var subscriptions: [PHEventSubscription] = []
/// Guards against stacking concurrent updates during rapid event bursts
/// (e.g. dragging the volume slider fires many events in quick succession).
var updateScheduled = false
private enum CodingKeys: String, CodingKey {
case command, name
case styleName = "style"
case refresh, events, centered
}
deinit {
intervalTask?.cancel()
subscriptions.forEach { $0.cancel() }
}
}
@@ -0,0 +1,10 @@
enum PHBlockKind: String {
case text, space
}
extension PHBlock {
var kind: PHBlockKind {
if command.starts(with: "_space") { return .space }
return .text
}
}