Compare commits

...

3 Commits

Author SHA1 Message Date
tommaso 34fc5a4c0d cleanup Block 2026-07-12 00:38:52 +02:00
tommaso 289cc6eb61 cleanup Config 2026-07-12 00:37:51 +02:00
tommaso d8d5e38d33 cleanup Theme 2026-07-12 00:37:28 +02:00
10 changed files with 229 additions and 241 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 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 {
/// 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)
}
}
}
+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
}
}
@@ -0,0 +1,56 @@
import Foundation
import TOML
extension PHConfig {
/// Load configuration from the default path (~/.config/phbar/config.toml).
/// If the file doesn't exist or fails to parse, defaults are used (non-fatal).
static func load() throws -> PHConfig {
let url = FileManager.default.homeDirectoryForCurrentUser.appending(
path: ".config/phbar/config.toml"
)
if FileManager.default.fileExists(atPath: url.relativePath) {
return try load(from: url)
} else {
return try loadFromBundle()
}
}
/// Load the bundled configuration.
/// If the file doesn't exist or fails to parse, the execution is interrupted.
static private func loadFromBundle() throws -> PHConfig {
guard
let defaultConfig = Bundle.module.url(
forResource: "config",
withExtension: "toml"
)?.resolvingSymlinksInPath()
else {
throw phbar.Error("Failed to load config file")
}
return try load(from: defaultConfig)
}
/// Decode configuration from a file at URL.
/// If the file doesn't exist or fails to parse, the execution is interrupted.
static private func load(from url: URL) throws -> PHConfig {
guard let data = try? Data(contentsOf: url),
let contents = String(data: data, encoding: .utf8)
else {
throw phbar.Error("Failed to load config file")
}
return try load(from: contents)
}
/// Decode configuration from a TOML string.
/// If the content fails to parse, the execution is interrupted.
static private func load(from contents: String) throws -> PHConfig {
do {
let decoder = TOMLDecoder()
let configFile = try decoder.decode(PHConfig.self, from: contents)
return configFile
} catch {
throw phbar.Error("Failed to parse config file", underlyingError: error)
}
}
}
@@ -1,62 +1,3 @@
import Foundation
import TOML
struct PHConfig: Decodable {
let text: PHConfigText
}
// Loading
extension PHConfig {
/// Load configuration from the default path (~/.config/phbar/config.toml).
/// If the file doesn't exist or fails to parse, defaults are used (non-fatal).
static func load() throws -> PHConfig {
let url = FileManager.default.homeDirectoryForCurrentUser.appending(
path: ".config/phbar/config.toml"
)
if FileManager.default.fileExists(atPath: url.relativePath) {
return try load(from: url)
} else {
return try loadFromBundle()
}
}
/// Load the bundled configuration.
/// If the file doesn't exist or fails to parse, the execution is interrupted.
static private func loadFromBundle() throws -> PHConfig {
guard
let defaultConfig = Bundle.module.url(
forResource: "config",
withExtension: "toml"
)?.resolvingSymlinksInPath()
else {
throw phbar.Error("Failed to load config file")
}
return try load(from: defaultConfig)
}
/// Decode configuration from a file at URL.
/// If the file doesn't exist or fails to parse, the execution is interrupted.
static private func load(from url: URL) throws -> PHConfig {
guard let data = try? Data(contentsOf: url),
let contents = String(data: data, encoding: .utf8)
else {
throw phbar.Error("Failed to load config file")
}
return try load(from: contents)
}
/// Decode configuration from a TOML string.
/// If the content fails to parse, the execution is interrupted.
static private func load(from contents: String) throws -> PHConfig {
do {
let decoder = TOMLDecoder()
let configFile = try decoder.decode(PHConfig.self, from: contents)
return configFile
} catch {
throw phbar.Error("Failed to parse config file", underlyingError: error)
}
}
}
@@ -8,7 +8,7 @@ struct PHConfigText: Decodable {
let style: PHConfigTextStyle
let offset: Double?
enum CodingKeys: String, CodingKey {
private enum CodingKeys: String, CodingKey {
case fontFamily = "font"
case size, weight, style, offset
}
@@ -0,0 +1,59 @@
import Foundation
import TOML
extension PHTheme {
/// Load theme from the config directory (~/.config/phbar/themes/).
/// If the file doesn't exist or fails to parse, defaults are used (non-fatal).
static func load(_ theme: String?) throws -> PHTheme {
guard let theme else { return try loadFromBundle() }
let url = FileManager.default.homeDirectoryForCurrentUser.appending(
path: ".config/phbar/themes/\(theme).toml"
)
if FileManager.default.fileExists(atPath: url.relativePath) {
return try load(from: url)
} else {
return try loadFromBundle()
}
}
/// Load the bundled theme.
/// If the file doesn't exist or fails to parse, the execution is interrupted.
static private func loadFromBundle() throws -> PHTheme {
guard
let defaultConfig = Bundle.module.url(
forResource: "theme",
withExtension: "toml"
)?.resolvingSymlinksInPath()
else {
throw phbar.Error("Failed to load theme file")
}
return try load(from: defaultConfig)
}
/// Load theme from a file at URL.
/// If the file doesn't exist or fails to parse, the execution is interrupted.
static private func load(from url: URL) throws -> PHTheme {
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 theme file", underlyingError: error)
}
}
/// Load theme from a TOML string.
/// If the content fails to parse, the execution is interrupted.
static private func load(from contents: String) throws -> PHTheme {
do {
let decoder = TOMLDecoder()
return try decoder.decode(PHTheme.self, from: contents)
} catch {
throw phbar.Error("Failed to parse theme file", underlyingError: error)
}
}
}
+1 -63
View File
@@ -1,71 +1,9 @@
import Foundation
import TOML
struct PHTheme: Decodable {
let window: PHThemeWindow
let styles: [PHThemeStyle]
enum CodingKeys: String, CodingKey {
private enum CodingKeys: String, CodingKey {
case window
case styles = "style"
}
}
// Loading
extension PHTheme {
/// Load theme from the config directory (~/.config/phbar/themes/).
/// If the file doesn't exist or fails to parse, defaults are used (non-fatal).
static func load(_ theme: String?) throws -> PHTheme {
guard let theme else { return try loadFromBundle() }
let url = FileManager.default.homeDirectoryForCurrentUser.appending(
path: ".config/phbar/themes/\(theme).toml"
)
if FileManager.default.fileExists(atPath: url.relativePath) {
return try load(from: url)
} else {
return try loadFromBundle()
}
}
/// Load the bundled theme.
/// If the file doesn't exist or fails to parse, the execution is interrupted.
static private func loadFromBundle() throws -> PHTheme {
guard
let defaultConfig = Bundle.module.url(
forResource: "theme",
withExtension: "toml"
)?.resolvingSymlinksInPath()
else {
throw phbar.Error("Failed to load theme file")
}
return try load(from: defaultConfig)
}
/// Load theme from a file at URL.
/// If the file doesn't exist or fails to parse, the execution is interrupted.
static private func load(from url: URL) throws -> PHTheme {
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 theme file", underlyingError: error)
}
}
/// Load theme from a TOML string.
/// If the content fails to parse, the execution is interrupted.
static private func load(from contents: String) throws -> PHTheme {
do {
let decoder = TOMLDecoder()
return try decoder.decode(PHTheme.self, from: contents)
} catch {
throw phbar.Error("Failed to parse theme file", underlyingError: error)
}
}
}
@@ -2,7 +2,7 @@ struct PHThemeWindow: Decodable {
let hasShadow: Bool
let blur: Double
enum CodingKeys: String, CodingKey {
private enum CodingKeys: String, CodingKey {
case hasShadow = "shadow"
case blur
}