Files
phbar/Sources/phbar/PHBar.swift
T
2026-07-13 11:43:37 +02:00

84 lines
3.1 KiB
Swift

// The Swift Programming Language
// https://docs.swift.org/swift-book
//
// Swift Argument Parser
// https://swiftpackageindex.com/apple/swift-argument-parser/documentation
import ArgumentParser
import Foundation
@main
struct PHBar: ParsableCommand {
static let configuration = CommandConfiguration(
commandName: "phbar",
abstract: "Modular status bar for macOS.",
discussion: """
phbar renders a status bar on every visible monitor. Each status bar
is dynamically built from the given theme, window configuration,
and block set. Thanks to the modularity of phbar, each one of these
elements can be overwritten for any given monitor.
phbar relies on blocks to determine what is visible on a bar and how
it should look. Everything in phbar is a block, not just texts but
also spaces and dividers. Each block can have a different visual style,
refresh conditions, and content source. The content of a block, if any,
is computed by calling a shell script, giving you the maximum flexibility.
Run `phbar install` to create the required config directory filled with
a template to start with. The directory will be created in one of these
locations in order of priority: $XDG_CONFIG_HOME/phbar, ~/.config/phbar,
or ~/.phbar.
Edit `<config-directory>/blocks/default.toml` to customize the block set used
by default and `<config-directory>/themes/default.toml` to customize the look
and feel of the bar. Check out the full documentation to learn more about all
the possible customisations.
Run `phbar start` to launch the bar and verify the appearance and behaviour.
Once you're ready to go to production, you can create a launch agent that uses
the same command to start the bar automatically at login.
""",
version: "1.0.0",
subcommands: [Start.self, Refresh.self]
)
}
extension PHBar {
struct Error: LocalizedError {
let message: String
let underlyingError: Swift.Error?
init(_ message: String, underlyingError: Swift.Error? = nil) {
self.message = message
self.underlyingError = underlyingError
}
var errorDescription: String? {
guard let underlyingError else { return message }
if let decodingError = underlyingError as? DecodingError {
return "\(message): \(Self.describe(decodingError))"
}
return "\(message): \(underlyingError.localizedDescription)"
}
private static func describe(_ error: DecodingError) -> String {
switch error {
case .keyNotFound(let key, let context):
let path = context.codingPath.map(\.stringValue).joined(separator: ".")
return "missing key '\(key.stringValue)' at \(path.isEmpty ? "root" : path)"
case .typeMismatch(let type, let context):
let path = context.codingPath.map(\.stringValue).joined(separator: ".")
return "type mismatch for \(type) at \(path)"
case .valueNotFound(let type, let context):
let path = context.codingPath.map(\.stringValue).joined(separator: ".")
return "missing value of type \(type) at \(path)"
case .dataCorrupted(let context):
return "corrupted data → \(context.debugDescription)"
@unknown default:
return error.localizedDescription
}
}
}
}