Compare commits
3 Commits
2d047d69b6
...
34fc5a4c0d
| Author | SHA1 | Date | |
|---|---|---|---|
|
34fc5a4c0d
|
|||
|
289cc6eb61
|
|||
|
d8d5e38d33
|
@@ -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 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user