cleanup Block
This commit is contained in:
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
-117
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user