From 34fc5a4c0d6d99c419d7d11ea27fb1d1a1d21ec6 Mon Sep 17 00:00:00 2001 From: Tommaso Negri Date: Sun, 12 Jul 2026 00:38:52 +0200 Subject: [PATCH] cleanup Block --- .../Block/Extensions/PHBlock+Loading.swift | 52 ++++++++ .../Extensions/PHBlock+Refresh.swift} | 117 ------------------ Sources/phbar/Models/Block/PHBlock.swift | 49 ++++++++ Sources/phbar/Models/Block/PHBlockKind.swift | 10 ++ 4 files changed, 111 insertions(+), 117 deletions(-) create mode 100644 Sources/phbar/Models/Block/Extensions/PHBlock+Loading.swift rename Sources/phbar/Models/{PHBlock.swift => Block/Extensions/PHBlock+Refresh.swift} (56%) create mode 100644 Sources/phbar/Models/Block/PHBlock.swift create mode 100644 Sources/phbar/Models/Block/PHBlockKind.swift diff --git a/Sources/phbar/Models/Block/Extensions/PHBlock+Loading.swift b/Sources/phbar/Models/Block/Extensions/PHBlock+Loading.swift new file mode 100644 index 0000000..b54e128 --- /dev/null +++ b/Sources/phbar/Models/Block/Extensions/PHBlock+Loading.swift @@ -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) + } + } +} diff --git a/Sources/phbar/Models/PHBlock.swift b/Sources/phbar/Models/Block/Extensions/PHBlock+Refresh.swift similarity index 56% rename from Sources/phbar/Models/PHBlock.swift rename to Sources/phbar/Models/Block/Extensions/PHBlock+Refresh.swift index 331c003..db1b746 100644 --- a/Sources/phbar/Models/PHBlock.swift +++ b/Sources/phbar/Models/Block/Extensions/PHBlock+Refresh.swift @@ -1,69 +1,4 @@ -import ArgumentParser 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? - - /// 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 { /// Start keeping the label up to date. @@ -187,55 +122,3 @@ extension PHBlock { 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) - } - } -} diff --git a/Sources/phbar/Models/Block/PHBlock.swift b/Sources/phbar/Models/Block/PHBlock.swift new file mode 100644 index 0000000..85b3a67 --- /dev/null +++ b/Sources/phbar/Models/Block/PHBlock.swift @@ -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? + + /// 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() } + } +} diff --git a/Sources/phbar/Models/Block/PHBlockKind.swift b/Sources/phbar/Models/Block/PHBlockKind.swift new file mode 100644 index 0000000..38e85da --- /dev/null +++ b/Sources/phbar/Models/Block/PHBlockKind.swift @@ -0,0 +1,10 @@ +enum PHBlockKind: String { + case text, space +} + +extension PHBlock { + var kind: PHBlockKind { + if command.starts(with: "_space") { return .space } + return .text + } +}