mirror of
https://github.com/m-housh/dotfiles.git
synced 2026-02-14 06:12:34 +00:00
wip
This commit is contained in:
9
dots/.gitignore
vendored
9
dots/.gitignore
vendored
@@ -1,9 +0,0 @@
|
|||||||
.DS_Store
|
|
||||||
/.build
|
|
||||||
/Packages
|
|
||||||
/*.xcodeproj
|
|
||||||
xcuserdata/
|
|
||||||
DerivedData/
|
|
||||||
.swiftpm/config/registries.json
|
|
||||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
|
||||||
.netrc
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
PREFIX ?= $(HOME)/.local
|
|
||||||
BINDIR = $(PREFIX)/bin
|
|
||||||
COMPLETIONDIR = $(PREFIX)/completions
|
|
||||||
LIBDIR = $(PREFIX)/lib
|
|
||||||
|
|
||||||
build:
|
|
||||||
swiftc ./scripts/build.swift
|
|
||||||
./build
|
|
||||||
rm ./build
|
|
||||||
|
|
||||||
install: build
|
|
||||||
install -d "$(BINDIR)" "$(LIBDIR)"
|
|
||||||
install ./.build/release/dots "$(BINDIR)"
|
|
||||||
|
|
||||||
uninstall:
|
|
||||||
rm "$(BINDIR)/dots"
|
|
||||||
rm "$(COMPLETIONDIR)/_dots"
|
|
||||||
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
// swift-tools-version: 5.7
|
|
||||||
|
|
||||||
|
|
||||||
import PackageDescription
|
|
||||||
|
|
||||||
let package = Package(
|
|
||||||
name: "dots",
|
|
||||||
platforms: [
|
|
||||||
.macOS(.v12)
|
|
||||||
],
|
|
||||||
products: [
|
|
||||||
.executable(name: "dots", targets: ["dots"]),
|
|
||||||
.library(name: "CliMiddleware", targets: ["CliMiddleware"]),
|
|
||||||
.library(name: "CliMiddlewareLive", targets: ["CliMiddlewareLive"]),
|
|
||||||
.library(name: "FileClient", targets: ["FileClient"]),
|
|
||||||
.library(name: "LoggingDependency", targets: ["LoggingDependency"]),
|
|
||||||
.library(name: "ShellClient", targets: ["ShellClient"]),
|
|
||||||
],
|
|
||||||
dependencies: [
|
|
||||||
.package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.2.0"),
|
|
||||||
.package(url: "https://github.com/pointfreeco/swift-dependencies.git", from: "0.1.4"),
|
|
||||||
.package(url: "https://github.com/apple/swift-log.git", from: "1.0.0"),
|
|
||||||
.package(url: "https://github.com/adorkable/swift-log-format-and-pipe.git", from: "0.1.0"),
|
|
||||||
],
|
|
||||||
targets: [
|
|
||||||
.target(
|
|
||||||
name: "CliMiddleware",
|
|
||||||
dependencies: [
|
|
||||||
.product(name: "Dependencies", package: "swift-dependencies"),
|
|
||||||
]
|
|
||||||
),
|
|
||||||
.target(
|
|
||||||
name: "CliMiddlewareLive",
|
|
||||||
dependencies: [
|
|
||||||
"CliMiddleware",
|
|
||||||
"FileClient",
|
|
||||||
"LoggingDependency",
|
|
||||||
"ShellClient"
|
|
||||||
]
|
|
||||||
),
|
|
||||||
.target(
|
|
||||||
name: "FileClient",
|
|
||||||
dependencies: [
|
|
||||||
.product(name: "Dependencies", package: "swift-dependencies"),
|
|
||||||
]
|
|
||||||
),
|
|
||||||
.target(
|
|
||||||
name: "LoggingDependency",
|
|
||||||
dependencies: [
|
|
||||||
.product(name: "Dependencies", package: "swift-dependencies"),
|
|
||||||
.product(name: "Logging", package: "swift-log"),
|
|
||||||
.product(name: "LoggingFormatAndPipe", package: "swift-log-format-and-pipe"),
|
|
||||||
]
|
|
||||||
),
|
|
||||||
.executableTarget(
|
|
||||||
name: "dots",
|
|
||||||
dependencies: [
|
|
||||||
"CliMiddlewareLive",
|
|
||||||
.product(name: "ArgumentParser", package: "swift-argument-parser"),
|
|
||||||
.product(name: "Dependencies", package: "swift-dependencies"),
|
|
||||||
]
|
|
||||||
),
|
|
||||||
.testTarget(
|
|
||||||
name: "dotsTests",
|
|
||||||
dependencies: ["dots"]
|
|
||||||
),
|
|
||||||
.target(
|
|
||||||
name: "ShellClient",
|
|
||||||
dependencies: [
|
|
||||||
"LoggingDependency",
|
|
||||||
.product(name: "Dependencies", package: "swift-dependencies")
|
|
||||||
]
|
|
||||||
),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# dots
|
|
||||||
|
|
||||||
A description of this package.
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
import Dependencies
|
|
||||||
import Foundation
|
|
||||||
import XCTestDynamicOverlay
|
|
||||||
|
|
||||||
/// Implements the logic for the `dots` command line tool.
|
|
||||||
///
|
|
||||||
/// Each command and it's sub-commands are implemented in the ``CliMiddlewareLive`` module. While this
|
|
||||||
/// represents the interface.
|
|
||||||
///
|
|
||||||
public struct CliMiddleware {
|
|
||||||
|
|
||||||
public var brew: (BrewContext) async throws -> Void
|
|
||||||
public var zsh: (ZshContext) async throws -> Void
|
|
||||||
|
|
||||||
public init(
|
|
||||||
brew: @escaping (BrewContext) async throws -> Void,
|
|
||||||
zsh: @escaping (ZshContext) async throws -> Void
|
|
||||||
) {
|
|
||||||
self.brew = brew
|
|
||||||
self.zsh = zsh
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct GlobalContext {
|
|
||||||
public let dryRun: Bool
|
|
||||||
|
|
||||||
public init(dryRun: Bool) {
|
|
||||||
self.dryRun = dryRun
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct BrewContext {
|
|
||||||
public let routes: [Route]
|
|
||||||
|
|
||||||
public init(
|
|
||||||
routes: [Route]
|
|
||||||
) {
|
|
||||||
self.routes = routes
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum Route: String, CaseIterable {
|
|
||||||
case all
|
|
||||||
case appStore
|
|
||||||
case brews
|
|
||||||
case casks
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct ZshContext {
|
|
||||||
|
|
||||||
public let context: Context
|
|
||||||
|
|
||||||
public init(
|
|
||||||
context: Context
|
|
||||||
) {
|
|
||||||
self.context = context
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum Context {
|
|
||||||
case install
|
|
||||||
case uninstall
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension CliMiddleware.GlobalContext: TestDependencyKey {
|
|
||||||
public static let testValue: CliMiddleware.GlobalContext = .init(dryRun: true)
|
|
||||||
}
|
|
||||||
|
|
||||||
extension CliMiddleware: TestDependencyKey {
|
|
||||||
|
|
||||||
public static let noop = Self.init(
|
|
||||||
brew: unimplemented("\(Self.self).brew"),
|
|
||||||
zsh: unimplemented("\(Self.self).zsh")
|
|
||||||
)
|
|
||||||
|
|
||||||
public static let testValue = CliMiddleware.noop
|
|
||||||
}
|
|
||||||
|
|
||||||
extension DependencyValues {
|
|
||||||
|
|
||||||
public var cliMiddleware: CliMiddleware {
|
|
||||||
get { self[CliMiddleware.self] }
|
|
||||||
set { self[CliMiddleware.self] = newValue }
|
|
||||||
}
|
|
||||||
|
|
||||||
public var globals: CliMiddleware.GlobalContext {
|
|
||||||
get { self[CliMiddleware.GlobalContext.self] }
|
|
||||||
set { self[CliMiddleware.GlobalContext.self] = newValue }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
import Dependencies
|
|
||||||
import CliMiddleware
|
|
||||||
import FileClient
|
|
||||||
import Foundation
|
|
||||||
import LoggingDependency
|
|
||||||
import ShellClient
|
|
||||||
|
|
||||||
struct Brew {
|
|
||||||
@Dependency(\.fileClient) var fileClient
|
|
||||||
@Dependency(\.globals.dryRun) var dryRun
|
|
||||||
@Dependency(\.logger) var logger
|
|
||||||
@Dependency(\.shellClient) var shellClient
|
|
||||||
|
|
||||||
let context: CliMiddleware.BrewContext
|
|
||||||
|
|
||||||
func run() async throws {
|
|
||||||
logger.info("Installing homebrew dependencies.")
|
|
||||||
for brewfile in try context.routes.brewfiles() {
|
|
||||||
logger.info("Installing dependencies from brewfile: \(brewfile.absoluteString)")
|
|
||||||
if !dryRun {
|
|
||||||
try shellClient.install(brewfile: brewfile)
|
|
||||||
logger.debug("Done installing dependencies from brewfile: \(brewfile.absoluteString)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
logger.info("Done installing homebrew dependencies.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension ShellClient {
|
|
||||||
|
|
||||||
func install(brewfile: URL) throws {
|
|
||||||
try foregroundShell(
|
|
||||||
"/opt/homebrew/bin/brew",
|
|
||||||
"bundle",
|
|
||||||
"--no-lock",
|
|
||||||
"--cleanup",
|
|
||||||
"--debug",
|
|
||||||
"--file",
|
|
||||||
brewfile.absoluteString
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fileprivate extension FileClient {
|
|
||||||
var brewFileDirectory: URL {
|
|
||||||
dotfilesDirectory()
|
|
||||||
.appendingPathComponent("macOS")
|
|
||||||
.appendingPathComponent(".config")
|
|
||||||
.appendingPathComponent("macOS")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fileprivate extension CliMiddleware.BrewContext.Route {
|
|
||||||
|
|
||||||
static func allBrews() throws -> [URL] {
|
|
||||||
let brews: [Self] = [.appStore, .brews, .casks]
|
|
||||||
return try brews.map { try $0.brewfile() }
|
|
||||||
}
|
|
||||||
|
|
||||||
func brewfile() throws -> URL {
|
|
||||||
@Dependency(\.fileClient) var fileClient
|
|
||||||
switch self {
|
|
||||||
case .all:
|
|
||||||
// should never happen.
|
|
||||||
throw BrewfileError()
|
|
||||||
case .appStore:
|
|
||||||
return fileClient.brewFileDirectory.appendingPathComponent("AppStore.Brewfile")
|
|
||||||
case .brews:
|
|
||||||
return fileClient.brewFileDirectory.appendingPathComponent("Brewfile")
|
|
||||||
case .casks:
|
|
||||||
return fileClient.brewFileDirectory.appendingPathComponent("Casks.Brewfile")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fileprivate extension Array where Element == CliMiddleware.BrewContext.Route {
|
|
||||||
|
|
||||||
func brewfiles() throws -> [URL] {
|
|
||||||
|
|
||||||
if self.count == 1 && self.first == .all {
|
|
||||||
return try CliMiddleware.BrewContext.Route.allBrews()
|
|
||||||
}
|
|
||||||
|
|
||||||
var urls = [URL]()
|
|
||||||
for route in self {
|
|
||||||
if route != .all {
|
|
||||||
let url = try route.brewfile()
|
|
||||||
urls.append(url)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return urls
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct BrewfileError: Error { }
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
import CliMiddleware
|
|
||||||
import Dependencies
|
|
||||||
import FileClient
|
|
||||||
import Foundation
|
|
||||||
import LoggingDependency
|
|
||||||
|
|
||||||
#if canImport(FoundationNetworking)
|
|
||||||
import FoundationNetworking
|
|
||||||
#endif
|
|
||||||
|
|
||||||
struct Zsh {
|
|
||||||
@Dependency(\.globals.dryRun) var dryRun
|
|
||||||
@Dependency(\.fileClient) var fileClient
|
|
||||||
@Dependency(\.logger) var logger
|
|
||||||
|
|
||||||
let context: CliMiddleware.ZshContext
|
|
||||||
|
|
||||||
func install() async throws {
|
|
||||||
let configString = fileClient.zshConfigDestination.absoluteString
|
|
||||||
.replacingOccurrences(of: "file://", with: "")
|
|
||||||
|
|
||||||
let destination = fileClient.zshEnvDestination
|
|
||||||
|
|
||||||
let destinationString = destination.absoluteString
|
|
||||||
.replacingOccurrences(of: "file://", with: "")
|
|
||||||
|
|
||||||
logger.info("Linking zsh configuration to: \(configString)")
|
|
||||||
logger.info("Linking .zshenv file to: \(destinationString)")
|
|
||||||
|
|
||||||
if !dryRun {
|
|
||||||
try await linkZshConfig()
|
|
||||||
try await fileClient.createSymlink(
|
|
||||||
source: fileClient.zshEnvSource,
|
|
||||||
destination: destination
|
|
||||||
)
|
|
||||||
}
|
|
||||||
logger.info("Done installing zsh configuration files.")
|
|
||||||
}
|
|
||||||
|
|
||||||
func uninstall() async throws {
|
|
||||||
logger.info("Uninstalling zsh configuration from: \(fileClient.zshConfigDestination.absoluteString)")
|
|
||||||
if !dryRun {
|
|
||||||
logger.debug("Moving configuration to the trash.")
|
|
||||||
try await fileClient.moveToTrash(fileClient.zshConfigDestination)
|
|
||||||
logger.debug("Moving .zshenv to the trash.")
|
|
||||||
try await fileClient.moveToTrash(fileClient.zshEnvDestination)
|
|
||||||
}
|
|
||||||
logger.info("Done uninstalling zsh configuration, you will need to reload your shell.")
|
|
||||||
}
|
|
||||||
|
|
||||||
func run() async throws {
|
|
||||||
switch context.context {
|
|
||||||
case .install:
|
|
||||||
try await self.install()
|
|
||||||
case .uninstall:
|
|
||||||
try await self.uninstall()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func linkZshConfig() async throws {
|
|
||||||
try await fileClient.createDirectory(at: fileClient.configDirectory())
|
|
||||||
try await fileClient.createSymlink(
|
|
||||||
source: fileClient.zshDirectory,
|
|
||||||
destination: fileClient.zshConfigDestination
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fileprivate extension FileClient {
|
|
||||||
var zshDirectory: URL {
|
|
||||||
dotfilesDirectory()
|
|
||||||
.appendingPathComponent("zsh")
|
|
||||||
.appendingPathComponent("config")
|
|
||||||
}
|
|
||||||
|
|
||||||
var zshConfigDestination: URL {
|
|
||||||
configDirectory().appendingPathComponent("zsh")
|
|
||||||
}
|
|
||||||
|
|
||||||
var zshEnvDestination: URL {
|
|
||||||
homeDirectory().appendingPathComponent(".zshenv")
|
|
||||||
}
|
|
||||||
|
|
||||||
var zshEnvSource: URL {
|
|
||||||
zshDirectory.appendingPathComponent(".zshenv")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import Dependencies
|
|
||||||
@_exported import CliMiddleware
|
|
||||||
@_exported import FileClient
|
|
||||||
@_exported import LoggingDependency
|
|
||||||
@_exported import ShellClient
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
#if canImport(FoundationNetworking)
|
|
||||||
import FoundationNetworking
|
|
||||||
#endif
|
|
||||||
|
|
||||||
extension CliMiddleware: DependencyKey {
|
|
||||||
public static var liveValue: CliMiddleware {
|
|
||||||
.init(
|
|
||||||
brew: { try await Brew(context: $0).run() },
|
|
||||||
zsh: { try await Zsh(context: $0).run() }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
import Dependencies
|
|
||||||
import Foundation
|
|
||||||
#if canImport(FoundationNetworking)
|
|
||||||
import FoundationNetworking
|
|
||||||
#endif
|
|
||||||
import XCTestDynamicOverlay
|
|
||||||
|
|
||||||
/// Represents interactions with the file system.
|
|
||||||
///
|
|
||||||
public struct FileClient {
|
|
||||||
|
|
||||||
public var configDirectory: () -> URL
|
|
||||||
public var createDirectory: (URL, Bool) async throws -> Void
|
|
||||||
public var createSymlink: (URL, URL) async throws -> Void
|
|
||||||
public var dotfilesDirectory: () -> URL
|
|
||||||
public var homeDirectory: () -> URL
|
|
||||||
public var exists: (URL) async throws -> Bool
|
|
||||||
public var readFile: (URL) async throws -> Data
|
|
||||||
public var moveToTrash: (URL) async throws -> Void
|
|
||||||
public var writeFile: (Data, URL) async throws -> Void
|
|
||||||
|
|
||||||
public func createDirectory(
|
|
||||||
at url: URL,
|
|
||||||
withIntermediates: Bool = true
|
|
||||||
) async throws {
|
|
||||||
let exists = try await self.exists(url)
|
|
||||||
if !exists {
|
|
||||||
try await createDirectory(url, withIntermediates)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func createSymlink(
|
|
||||||
source: URL,
|
|
||||||
destination: URL
|
|
||||||
) async throws {
|
|
||||||
try await self.createSymlink(source, destination)
|
|
||||||
}
|
|
||||||
|
|
||||||
public func read(file: URL) async throws -> Data {
|
|
||||||
try await self.readFile(file)
|
|
||||||
}
|
|
||||||
|
|
||||||
public func write(data: Data, to file: URL) async throws {
|
|
||||||
try await writeFile(data, file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension FileClient: TestDependencyKey {
|
|
||||||
public static let noop = Self.init(
|
|
||||||
configDirectory: unimplemented(placeholder: URL(string: "/")!),
|
|
||||||
createDirectory: unimplemented(),
|
|
||||||
createSymlink: unimplemented(),
|
|
||||||
dotfilesDirectory: unimplemented(placeholder: URL(string: "/")!),
|
|
||||||
homeDirectory: unimplemented(),
|
|
||||||
exists: unimplemented(placeholder: false),
|
|
||||||
readFile: unimplemented(placeholder: Data()),
|
|
||||||
moveToTrash: unimplemented(),
|
|
||||||
writeFile: unimplemented()
|
|
||||||
)
|
|
||||||
|
|
||||||
public static let testValue: FileClient = .noop
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension DependencyValues {
|
|
||||||
public var fileClient: FileClient {
|
|
||||||
get { self[FileClient.self] }
|
|
||||||
set { self[FileClient.self] = newValue }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
import Dependencies
|
|
||||||
import Foundation
|
|
||||||
#if canImport(FoundationNetworking)
|
|
||||||
import FoundationNetworking
|
|
||||||
#endif
|
|
||||||
|
|
||||||
extension FileClient: DependencyKey {
|
|
||||||
|
|
||||||
public static var liveValue: FileClient {
|
|
||||||
.live(environment: ProcessInfo.processInfo.environment)
|
|
||||||
}
|
|
||||||
|
|
||||||
public static func live(environment: [String: String] = [:]) -> Self {
|
|
||||||
let environment = Environment(environment: environment)
|
|
||||||
return .init(
|
|
||||||
configDirectory: {
|
|
||||||
guard let xdgConfigHome = environment.xdgConfigHome,
|
|
||||||
let configUrl = URL(string: xdgConfigHome)
|
|
||||||
else {
|
|
||||||
return FileManager.default.homeDirectoryForCurrentUser
|
|
||||||
.appendingPathComponent(".config")
|
|
||||||
}
|
|
||||||
return configUrl
|
|
||||||
},
|
|
||||||
createDirectory: { url, withIntermediates in
|
|
||||||
try FileManager.default.createDirectory(
|
|
||||||
at: url,
|
|
||||||
withIntermediateDirectories: withIntermediates
|
|
||||||
)
|
|
||||||
},
|
|
||||||
createSymlink: { source, destination in
|
|
||||||
try FileManager.default.createSymbolicLink(
|
|
||||||
at: source,
|
|
||||||
withDestinationURL: destination
|
|
||||||
)
|
|
||||||
},
|
|
||||||
dotfilesDirectory: {
|
|
||||||
guard let dotfiles = environment.dotfilesDirectory,
|
|
||||||
let dotfilesUrl = URL(string: dotfiles)
|
|
||||||
else {
|
|
||||||
return FileManager.default.homeDirectoryForCurrentUser
|
|
||||||
.appendingPathComponent(".dotfiles")
|
|
||||||
}
|
|
||||||
return dotfilesUrl
|
|
||||||
},
|
|
||||||
homeDirectory: {
|
|
||||||
FileManager.default.homeDirectoryForCurrentUser
|
|
||||||
},
|
|
||||||
exists: { path in
|
|
||||||
FileManager.default.fileExists(atPath: path.absoluteString)
|
|
||||||
},
|
|
||||||
readFile: { path in
|
|
||||||
try Data(contentsOf: path)
|
|
||||||
},
|
|
||||||
moveToTrash: { path in
|
|
||||||
try FileManager.default.trashItem(at: path, resultingItemURL: nil)
|
|
||||||
},
|
|
||||||
writeFile: { data, path in
|
|
||||||
try data.write(to: path)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fileprivate struct Environment {
|
|
||||||
let xdgConfigHome: String?
|
|
||||||
let dotfilesDirectory: String?
|
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey {
|
|
||||||
case xdgConfigHome = "XDG_CONFIG_HOME"
|
|
||||||
case dotfilesDirectory = "DOTFILES"
|
|
||||||
}
|
|
||||||
|
|
||||||
init(environment: [String: String]) {
|
|
||||||
self.xdgConfigHome = environment[CodingKeys.xdgConfigHome.rawValue]
|
|
||||||
self.dotfilesDirectory = environment[CodingKeys.dotfilesDirectory.rawValue]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
import Dependencies
|
|
||||||
import Foundation
|
|
||||||
@_exported import Logging
|
|
||||||
import LoggingFormatAndPipe
|
|
||||||
|
|
||||||
extension Logger: DependencyKey {
|
|
||||||
|
|
||||||
fileprivate static func factory(label: String) -> Self {
|
|
||||||
Logger(label: "dots") { _ in
|
|
||||||
LoggingFormatAndPipe.Handler(
|
|
||||||
formatter: BasicFormatter([.message]),
|
|
||||||
pipe: LoggerTextOutputStreamPipe.standardOutput
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static var liveValue: Logger {
|
|
||||||
factory(label: "dots")
|
|
||||||
}
|
|
||||||
|
|
||||||
public static var testValue: Logger {
|
|
||||||
factory(label: "dots-test")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension DependencyValues {
|
|
||||||
public var logger: Logger {
|
|
||||||
get { self[Logger.self] }
|
|
||||||
set { self[Logger.self] = newValue }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
import Dependencies
|
|
||||||
import Foundation
|
|
||||||
import XCTestDynamicOverlay
|
|
||||||
#if canImport(FoundationNetworking)
|
|
||||||
import FoundationNetworking
|
|
||||||
#endif
|
|
||||||
|
|
||||||
public struct ShellClient {
|
|
||||||
public var foregroundShell: ([String]) throws -> Void
|
|
||||||
public var backgroundShell: ([String]) throws -> String
|
|
||||||
|
|
||||||
public func foregroundShell(_ arguments: String...) throws {
|
|
||||||
try self.foregroundShell(arguments)
|
|
||||||
}
|
|
||||||
|
|
||||||
@discardableResult
|
|
||||||
public func backgroundShell(_ arguments: String...) throws -> String {
|
|
||||||
try self.backgroundShell(arguments)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension ShellClient: TestDependencyKey {
|
|
||||||
|
|
||||||
public static let noop = Self.init(
|
|
||||||
foregroundShell: unimplemented(),
|
|
||||||
backgroundShell: unimplemented(placeholder: "")
|
|
||||||
)
|
|
||||||
|
|
||||||
public static let testValue: ShellClient = .noop
|
|
||||||
}
|
|
||||||
|
|
||||||
extension DependencyValues {
|
|
||||||
public var shellClient: ShellClient {
|
|
||||||
get { self[ShellClient.self] }
|
|
||||||
set { self[ShellClient.self] = newValue }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
import Dependencies
|
|
||||||
import Foundation
|
|
||||||
import LoggingDependency
|
|
||||||
#if canImport(FoundationNetworking)
|
|
||||||
import FoundationNetworking
|
|
||||||
#endif
|
|
||||||
|
|
||||||
extension ShellClient: DependencyKey {
|
|
||||||
|
|
||||||
public static var liveValue: ShellClient {
|
|
||||||
@Dependency(\.logger) var logger
|
|
||||||
|
|
||||||
return .init(
|
|
||||||
foregroundShell: { arguments in
|
|
||||||
logger.debug("Running in foreground shell.")
|
|
||||||
logger.debug("$ \(arguments.joined(separator: " "))")
|
|
||||||
|
|
||||||
let task = Process()
|
|
||||||
task.launchPath = "/usr/bin/env"
|
|
||||||
task.arguments = arguments
|
|
||||||
task.launch()
|
|
||||||
task.waitUntilExit()
|
|
||||||
|
|
||||||
guard task.terminationStatus == 0 else {
|
|
||||||
throw ShellError(terminationStatus: task.terminationStatus)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
backgroundShell: { arguments in
|
|
||||||
logger.debug("Running background shell.")
|
|
||||||
logger.debug("$ \(arguments.joined(separator: " "))")
|
|
||||||
|
|
||||||
let task = Process()
|
|
||||||
task.launchPath = "/usr/bin/env"
|
|
||||||
task.arguments = arguments
|
|
||||||
// grab stdout
|
|
||||||
let output = Pipe()
|
|
||||||
task.standardOutput = output
|
|
||||||
// ignore stderr
|
|
||||||
let error = Pipe()
|
|
||||||
task.standardError = error
|
|
||||||
task.launch()
|
|
||||||
task.waitUntilExit()
|
|
||||||
|
|
||||||
guard task.terminationStatus == 0 else {
|
|
||||||
throw ShellError(terminationStatus: task.terminationStatus)
|
|
||||||
}
|
|
||||||
|
|
||||||
return String(decoding: output.fileHandleForReading.readDataToEndOfFile(), as: UTF8.self)
|
|
||||||
.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
||||||
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ShellError: Swift.Error {
|
|
||||||
var terminationStatus: Int32
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import ArgumentParser
|
|
||||||
import Dependencies
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
struct CliContext {
|
|
||||||
let globals: GlobalOptions
|
|
||||||
let _run: () async throws -> Void
|
|
||||||
|
|
||||||
init(globals: GlobalOptions, run: @escaping () async throws -> Void) {
|
|
||||||
self.globals = globals
|
|
||||||
self._run = run
|
|
||||||
}
|
|
||||||
|
|
||||||
func run() async throws {
|
|
||||||
try await withDependencies {
|
|
||||||
if globals.verbose {
|
|
||||||
$0.logger.logLevel = .debug
|
|
||||||
}
|
|
||||||
$0.globals = .live(globals)
|
|
||||||
} operation: {
|
|
||||||
try await _run()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
import ArgumentParser
|
|
||||||
import CliMiddleware
|
|
||||||
import Dependencies
|
|
||||||
import Foundation
|
|
||||||
import LoggingDependency
|
|
||||||
|
|
||||||
extension Dots {
|
|
||||||
|
|
||||||
struct Brew: AsyncParsableCommand {
|
|
||||||
static var configuration = CommandConfiguration(
|
|
||||||
abstract: "Manage homebrew dependency installation.",
|
|
||||||
subcommands: [
|
|
||||||
Install.self
|
|
||||||
],
|
|
||||||
defaultSubcommand: Install.self
|
|
||||||
)
|
|
||||||
|
|
||||||
struct Install: AsyncParsableCommand {
|
|
||||||
|
|
||||||
static let configuration = CommandConfiguration(
|
|
||||||
abstract: "Install brew dependencies from the brewfiles."
|
|
||||||
)
|
|
||||||
|
|
||||||
@OptionGroup var globals: GlobalOptions
|
|
||||||
|
|
||||||
@Flag(help: "The homebrew dependencies to install from their brewfiles.")
|
|
||||||
var routes: [CliMiddleware.BrewContext.Route] = [.all]
|
|
||||||
|
|
||||||
func run() async throws {
|
|
||||||
try await CliContext(globals: globals) {
|
|
||||||
@Dependency(\.cliMiddleware.brew) var brew
|
|
||||||
@Dependency(\.logger) var logger: Logger
|
|
||||||
|
|
||||||
logger.debug("Routes: \(routes)")
|
|
||||||
try await brew(.init(routes: routes))
|
|
||||||
logger.info("Done.")
|
|
||||||
}
|
|
||||||
.run()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension CliMiddleware.BrewContext.Route: EnumerableFlag { }
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
import ArgumentParser
|
|
||||||
import CliMiddleware
|
|
||||||
import Dependencies
|
|
||||||
import Foundation
|
|
||||||
import LoggingDependency
|
|
||||||
|
|
||||||
extension Dots {
|
|
||||||
struct Zsh: AsyncParsableCommand {
|
|
||||||
static let configuration = CommandConfiguration(
|
|
||||||
abstract: "Manage zsh configuration.",
|
|
||||||
subcommands: [
|
|
||||||
Install.self,
|
|
||||||
Uninstall.self
|
|
||||||
],
|
|
||||||
defaultSubcommand: Install.self
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
struct Install: AsyncParsableCommand {
|
|
||||||
static let configuration = CommandConfiguration(
|
|
||||||
abstract: "Install zsh configuration files."
|
|
||||||
)
|
|
||||||
|
|
||||||
@OptionGroup var globals: GlobalOptions
|
|
||||||
|
|
||||||
func run() async throws {
|
|
||||||
try await CliContext(globals: globals) {
|
|
||||||
@Dependency(\.cliMiddleware.zsh) var zsh
|
|
||||||
@Dependency(\.logger) var logger: Logger
|
|
||||||
|
|
||||||
try await zsh(.init(context: .install))
|
|
||||||
logger.info("Done.")
|
|
||||||
}
|
|
||||||
.run()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Uninstall: AsyncParsableCommand {
|
|
||||||
static let configuration = CommandConfiguration(
|
|
||||||
abstract: "Uninstall zsh configuration files."
|
|
||||||
)
|
|
||||||
|
|
||||||
@OptionGroup var globals: GlobalOptions
|
|
||||||
|
|
||||||
func run() async throws {
|
|
||||||
try await CliContext(globals: globals) {
|
|
||||||
@Dependency(\.cliMiddleware.zsh) var zsh
|
|
||||||
@Dependency(\.logger) var logger: Logger
|
|
||||||
|
|
||||||
try await zsh(.init(context: .uninstall))
|
|
||||||
logger.info("Done.")
|
|
||||||
}
|
|
||||||
.run()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import ArgumentParser
|
|
||||||
import CliMiddleware
|
|
||||||
import Dependencies
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
struct GlobalOptions: ParsableArguments {
|
|
||||||
@Flag(
|
|
||||||
name: .long,
|
|
||||||
help: "Perform an action as a dry-run, not removing or installing anything."
|
|
||||||
)
|
|
||||||
var dryRun: Bool = false
|
|
||||||
|
|
||||||
@Flag(
|
|
||||||
name: .long,
|
|
||||||
help: "Increase logging output level."
|
|
||||||
)
|
|
||||||
var verbose: Bool = false
|
|
||||||
}
|
|
||||||
|
|
||||||
extension CliMiddleware.GlobalContext {
|
|
||||||
static func live(_ globalOptions: GlobalOptions) -> Self {
|
|
||||||
.init(dryRun: globalOptions.dryRun)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
// Do not change this value, it get's set by the build script
|
|
||||||
let VERSION: String? = nil
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import ArgumentParser
|
|
||||||
|
|
||||||
@main
|
|
||||||
struct Dots: AsyncParsableCommand {
|
|
||||||
static var configuration = CommandConfiguration(
|
|
||||||
abstract: "Commands for installing / uninstalling dotfile configuration.",
|
|
||||||
version: VERSION ?? "0.0.0",
|
|
||||||
subcommands: [
|
|
||||||
Brew.self,
|
|
||||||
Zsh.self
|
|
||||||
]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
import XCTest
|
|
||||||
@testable import dots
|
|
||||||
|
|
||||||
final class dotsTests: XCTestCase {
|
|
||||||
func testExample() throws {
|
|
||||||
XCTAssert(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
#!/usr/bin/env swift
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
try build()
|
|
||||||
|
|
||||||
func build() throws {
|
|
||||||
try withVersion(in: "Sources/dots/Version.swift", as: currentVersion()) {
|
|
||||||
try foregroundShell(
|
|
||||||
"swift", "build",
|
|
||||||
"--disable-sandbox",
|
|
||||||
"--configuration", "release",
|
|
||||||
"-Xswiftc", "-cross-module-optimization"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func withVersion(in file: String, as version: String, _ closure: () throws -> ()) throws {
|
|
||||||
let fileURL = URL(fileURLWithPath: file)
|
|
||||||
let originalFileContents = try String(contentsOf: fileURL, encoding: .utf8)
|
|
||||||
// set version
|
|
||||||
try originalFileContents
|
|
||||||
.replacingOccurrences(of: "nil", with: "\"\(version)\"")
|
|
||||||
.write(to: fileURL, atomically: true, encoding: .utf8)
|
|
||||||
defer {
|
|
||||||
// undo set version
|
|
||||||
try! originalFileContents
|
|
||||||
.write(to: fileURL, atomically: true, encoding: .utf8)
|
|
||||||
}
|
|
||||||
// run closure
|
|
||||||
try closure()
|
|
||||||
}
|
|
||||||
|
|
||||||
func currentVersion() throws -> String {
|
|
||||||
do {
|
|
||||||
let tag = try backgroundShell("git", "describe", "--tags", "--exact-match")
|
|
||||||
return tag
|
|
||||||
} catch {
|
|
||||||
let branch = try backgroundShell("git", "symbolic-ref", "-q", "--short", "HEAD")
|
|
||||||
let commit = try backgroundShell("git", "rev-parse", "--short", "HEAD")
|
|
||||||
return "\(branch) (\(commit))"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func foregroundShell(_ args: String...) throws {
|
|
||||||
print("$", args.joined(separator: " "))
|
|
||||||
let task = Process()
|
|
||||||
task.launchPath = "/usr/bin/env"
|
|
||||||
task.arguments = args
|
|
||||||
task.launch()
|
|
||||||
task.waitUntilExit()
|
|
||||||
|
|
||||||
guard task.terminationStatus == 0 else {
|
|
||||||
throw ShellError(terminationStatus: task.terminationStatus)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@discardableResult
|
|
||||||
func backgroundShell(_ args: String...) throws -> String {
|
|
||||||
let task = Process()
|
|
||||||
task.launchPath = "/usr/bin/env"
|
|
||||||
task.arguments = args
|
|
||||||
// grab stdout
|
|
||||||
let output = Pipe()
|
|
||||||
task.standardOutput = output
|
|
||||||
// ignore stderr
|
|
||||||
let error = Pipe()
|
|
||||||
task.standardError = error
|
|
||||||
task.launch()
|
|
||||||
task.waitUntilExit()
|
|
||||||
|
|
||||||
guard task.terminationStatus == 0 else {
|
|
||||||
throw ShellError(terminationStatus: task.terminationStatus)
|
|
||||||
}
|
|
||||||
|
|
||||||
return String(decoding: output.fileHandleForReading.readDataToEndOfFile(), as: UTF8.self)
|
|
||||||
.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ShellError: Swift.Error {
|
|
||||||
var terminationStatus: Int32
|
|
||||||
}
|
|
||||||
@@ -1,2 +1,2 @@
|
|||||||
mas "pwSafe", id: "520993579"
|
mas "pwSafe", id: 520993579
|
||||||
mas "Home Assistant", id: "1099568401"
|
mas "Home Assistant", id: 1099568401
|
||||||
|
|||||||
Reference in New Issue
Block a user