Skip to content

Commit

Permalink
WIP! Thu Oct 5 01:14:09 CEST 2023
Browse files Browse the repository at this point in the history
  • Loading branch information
nikitabobko committed Oct 4, 2023
1 parent 18ed447 commit 7d51734
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 56 deletions.
4 changes: 4 additions & 0 deletions AeroSpace.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
45AA5FD4A023AF751922BC22 /* BundleEx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B7A2DF0D1F72B80B1F04240 /* BundleEx.swift */; };
45EA2D1C90430C432E123B51 /* keysMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C0D40CBD65704BA9595C2FA /* keysMap.swift */; };
56E72B24303F5F337B31B776 /* TrayMenuModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F5F52E346D024960EAF5938 /* TrayMenuModel.swift */; };
598F12005E4D15FF1A479181 /* ResultEx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 598F185C1BAF2E829B5164CC /* ResultEx.swift */; };
5DA2DA21600E8B5BCA3DCFC0 /* LayoutCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A2B673C67B00DBFCC27FFE7 /* LayoutCommand.swift */; };
6317AB471F4C4F5D66A25784 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EEDBFFCA7A77D96B18FB0732 /* Assets.xcassets */; };
635733FDDF37E44364372B74 /* MruStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = 954A434EE57D76F5A9D4140D /* MruStack.swift */; };
Expand Down Expand Up @@ -98,6 +99,7 @@
5274C575044C2A7123C57584 /* AeroSpace-Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "AeroSpace-Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
53FDECECC773EBA30661EB8A /* FlattenWorkspaceTreeCommandTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlattenWorkspaceTreeCommandTest.swift; sourceTree = "<group>"; };
569422C0C4C23EF3E024C8E6 /* ExecAndForgetCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExecAndForgetCommand.swift; sourceTree = "<group>"; };
598F185C1BAF2E829B5164CC /* ResultEx.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResultEx.swift; sourceTree = "<group>"; };
5F5F52E346D024960EAF5938 /* TrayMenuModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrayMenuModel.swift; sourceTree = "<group>"; };
6352ADEE6625D9703CFCA99A /* Window.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Window.swift; sourceTree = "<group>"; };
67B9FFF81EB0327ABD51A7FE /* MoveThroughCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoveThroughCommand.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -301,6 +303,7 @@
28B788A95DD3C267878E05B5 /* Rect.swift */,
AAE5DCAEC5EE619CE33859E7 /* SequenceEx.swift */,
976540EBEACF846D598CD6E1 /* util.swift */,
598F185C1BAF2E829B5164CC /* ResultEx.swift */,
);
path = util;
sourceTree = "<group>";
Expand Down Expand Up @@ -461,6 +464,7 @@
B3702BB393A9B03CCAE4C60E /* refresh.swift in Sources */,
ED96E36786C941AB3AF780BC /* startAtLogin.swift in Sources */,
93D44EA41776738B4758C28D /* util.swift in Sources */,
598F12005E4D15FF1A479181 /* ResultEx.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
9 changes: 6 additions & 3 deletions src/command/parseCommand.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import TOMLKit

typealias ParsedCommand<T> = Result<T, String>
extension String: Error {}

// todo drop TomlBacktrace
func parseCommand(_ raw: TOMLValueConvertible, _ backtrace: TomlBacktrace) -> Command {
if let rawString = raw.string {
Expand Down Expand Up @@ -40,7 +43,7 @@ private func parseSingleCommand(_ raw: String, _ backtrace: TomlBacktrace) -> Co
?? errorT("\(backtrace): Can't parse '\(firstWord)' direction")
return MoveThroughCommand(direction: direction)
} else if firstWord == "layout" {
return LayoutCommand(toggleBetween: args.map { parseLayout(String($0), backtrace) })
return LayoutCommand(toggleBetween: args.map { parseLayout(String($0)) })
?? errorT("\(backtrace): Can't create layout command") // todo nicer message
} else if raw == "workspace-back-and-forth" {
return WorkspaceBackAndForthCommand()
Expand All @@ -57,8 +60,8 @@ private func parseSingleCommand(_ raw: String, _ backtrace: TomlBacktrace) -> Co
}
}

func parseLayout(_ raw: String, _ backtrace: TomlBacktrace) -> ConfigLayout {
ConfigLayout(rawValue: raw) ?? errorT("\(backtrace): Can't parse layout '\(raw)'")
func parseLayout(_ raw: String) -> ParsedCommand<ConfigLayout> {
ConfigLayout(rawValue: raw).orFailure { "Can't parse layout '\(raw)'" }
}

private func parseSingleArg(_ args: ArraySlice<Swift.String.SubSequence>, _ command: String, _ backtrace: TomlBacktrace) -> String {
Expand Down
190 changes: 137 additions & 53 deletions src/config/parseConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,56 @@ func reloadConfig() {
syncStartAtLogin()
}

func parseConfig(_ rawToml: String) -> Config {
struct TomlParseError: Error {
let backtrace: TomlBacktrace
let message: String

init(_ backtrace: TomlBacktrace, _ message: String) {
self.backtrace = backtrace
self.message = message
}
}
//extension [TomlParseError]: Error {}

private typealias ParsedTomlResult<T> = Result<T, TomlParseError>

private extension Result {
func unwrap(_ existingErrors: [Failure]) -> (Success?, [Failure]) {
switch self {
case .success(let success):
return (success, existingErrors)
case .failure(let error):
return (nil, existingErrors + [error])
}
}
}

typealias ParsedTomlWriter<T> = Writer<T, TomlParseError>

struct Writer<T, L> {
let value: T
let log: [L]
}

//private struct Writer<T> { // Writer monad
// let data: T
// let errors: [ParseError]
// init(_ data: T, _ errors: [ParseError]) {
// self.data = data
// self.errors = errors
// }
// init(_ data: T) { self.init(data, []) }
//}

//extension Writer {
// func toTuple() -> (T, [ParseError]) { (data, errors) }
// func flatMap<R>(_ transformer: (T) -> Writer<R>) -> Writer<R> {
// let writer = transformer(data)
// return Writer<R>(writer.data, errors + writer.errors)
// }
//}

func parseConfig(_ rawToml: String) -> ParsedTomlWriter<Config> {
let rawTable: TOMLTable
do {
rawTable = try TOMLTable(string: rawToml)
Expand All @@ -21,6 +70,7 @@ func parseConfig(_ rawToml: String) -> Config {
}

var modes: [String: Mode]? = nil
var errors: [TomlParseError] = []

let key1 = "after-startup-command"
var value1: Command? = nil
Expand Down Expand Up @@ -61,25 +111,25 @@ func parseConfig(_ rawToml: String) -> Config {
case key1:
value1 = parseCommand(value, backtrace)
case key2:
value2 = parseBool(value, backtrace)
(value2, errors) = parseBool(value, backtrace).unwrap(errors)
case key3:
value3 = parseBool(value, backtrace)
(value3, errors) = parseBool(value, backtrace).unwrap(errors)
case key4:
value4 = parseBool(value, backtrace)
(value4, errors) = parseBool(value, backtrace).unwrap(errors)
case key5:
value5 = parseMainLayout(value, backtrace)
(value5, errors) = parseMainLayout(value, backtrace).unwrap(errors)
case key6:
value6 = parseFocusWrapping(value, backtrace)
(value6, errors) = parseFocusWrapping(value, backtrace).unwrap(errors)
case key7:
value7 = parseBool(value, backtrace)
(value7, errors) = parseBool(value, backtrace).unwrap(errors)
case key8:
value8 = parseBool(value, backtrace)
(value8, errors) = parseBool(value, backtrace).unwrap(errors)
case key9:
value9 = parseCommand(value, backtrace)
case key10:
value10 = parseTrayIconContent(value, backtrace)
(value10, errors) = parseTrayIconContent(value, backtrace).unwrap(errors)
case key11:
value11 = parseString(value, backtrace)
(value11, errors) = parseString(value, backtrace).unwrap(errors)
case "mode":
modes = parseModes(value, backtrace)
default:
Expand Down Expand Up @@ -110,28 +160,36 @@ func parseConfig(_ rawToml: String) -> Config {
)
}

private func parseString(_ raw: TOMLValueConvertible, _ backtrace: TomlBacktrace) -> String {
let rawString = raw.string ?? expectedActualTypeError(expected: .string, actual: raw.type, backtrace)
return rawString
private func parseString(_ raw: TOMLValueConvertible, _ backtrace: TomlBacktrace) -> ParsedTomlResult<String> {
raw.string.orFailure { expectedActualTypeError(expected: .string, actual: raw.type, backtrace) }
}

private func parseTrayIconContent(_ raw: TOMLValueConvertible, _ backtrace: TomlBacktrace) -> TrayIconContent {
TrayIconContent(rawValue: parseString(raw, backtrace)) ?? errorT("\(backtrace): Can't parse tray-icon-content")
private func parseTrayIconContent(_ raw: TOMLValueConvertible, _ backtrace: TomlBacktrace) -> ParsedTomlResult<TrayIconContent> {
parseString(raw, backtrace).flatMap {
TrayIconContent(rawValue: $0).orFailure { TomlParseError(backtrace, "Can't parse tray-icon-content") }
}
}

private func parseFocusWrapping(_ raw: TOMLValueConvertible, _ backtrace: TomlBacktrace) -> FocusWrapping {
FocusWrapping(rawValue: parseString(raw, backtrace)) ?? errorT("\(backtrace): Can't parse focus wrapping")
private func parseFocusWrapping(_ raw: TOMLValueConvertible, _ backtrace: TomlBacktrace) -> ParsedTomlResult<FocusWrapping> {
parseString(raw, backtrace).flatMap {
FocusWrapping(rawValue: $0).orFailure { TomlParseError(backtrace, "Can't parse focus wrapping") }
}
}

private func parseMainLayout(_ raw: TOMLValueConvertible, _ backtrace: TomlBacktrace) -> ConfigLayout {
let layout = parseLayout(parseString(raw, backtrace), backtrace)
if layout == .main {
error("\(backtrace): main layout can't be '\(layout)'")
}
return layout
private func parseMainLayout(_ raw: TOMLValueConvertible, _ backtrace: TomlBacktrace) -> ParsedTomlResult<ConfigLayout> {
parseString(raw, backtrace)
.flatMap { parseLayout($0).mapError { TomlParseError(backtrace, $0) } }
.flatMap { (layout: ConfigLayout) -> ParsedTomlResult<ConfigLayout> in
layout == .main ? .failure(TomlParseError(backtrace, "main layout can't be 'main'")) : .success(layout)
}
}

private func parseModes(_ raw: TOMLValueConvertible, _ backtrace: TomlBacktrace) -> [String: Mode] {
raw.table.orFailure { expectedActualTypeError(expected: .table, actual: raw.type, backtrace) }




let rawTable = raw.table ?? expectedActualTypeError(expected: .table, actual: raw.type, backtrace)
var result: [String: Mode] = [:]
for (key, value) in rawTable {
Expand All @@ -143,28 +201,31 @@ private func parseModes(_ raw: TOMLValueConvertible, _ backtrace: TomlBacktrace)
return result
}

private func parseMode(_ raw: TOMLValueConvertible, _ backtrace: TomlBacktrace) -> Mode {
let rawTable = raw.table ?? expectedActualTypeError(expected: .table, actual: raw.type, backtrace)

let key1 = "binding"
var value1: [HotkeyBinding] = []

for (key, value) in rawTable {
let keyBacktrace = backtrace + .key(key)
switch key {
case key1:
value1 = parseBindings(value, keyBacktrace)
default:
unknownKeyError(keyBacktrace)
private func parseMode(_ raw: TOMLValueConvertible, _ backtrace: TomlBacktrace) -> ParsedTomlResult<Mode> {
raw.table.orFailure { expectedActualTypeError(expected: .table, actual: raw.type, backtrace) }
.flatMap { (rawTable: TOMLTable) -> ParsedTomlResult<Mode> in
var errors: [TomlParseError] = []

let key1 = "binding"
var value1: [HotkeyBinding]? = nil

for (key, value) in rawTable {
let keyBacktrace = backtrace + .key(key)
switch key {
case key1:
(value1, errors) = parseBindings(value, keyBacktrace).unwrap(errors)
default:
errors += [unknownKeyError(keyBacktrace)]
}
}
return Mode(
name: nil,
bindings: value1 ?? []
)
}
}
return Mode(
name: nil,
bindings: value1
)
}

private func parseBindings(_ raw: TOMLValueConvertible, _ backtrace: TomlBacktrace) -> [HotkeyBinding] {
private func parseBindings(_ raw: TOMLValueConvertible, _ backtrace: TomlBacktrace) -> ParsedTomlWriter<[HotkeyBinding]> {
let rawTable = raw.table ?? expectedActualTypeError(expected: .table, actual: raw.type, backtrace)
return rawTable.map { (binding: String, value: TOMLValueConvertible) in
let keyBacktrace = backtrace + .key(binding)
Expand All @@ -173,16 +234,39 @@ private func parseBindings(_ raw: TOMLValueConvertible, _ backtrace: TomlBacktra
}
}

private func parseBinding(_ raw: String, _ backtrace: TomlBacktrace) -> (NSEvent.ModifierFlags, Key) {
private func parseBinding(_ raw: String, _ backtrace: TomlBacktrace) -> ParsedTomlResult<(NSEvent.ModifierFlags, Key)> {
let rawKeys = raw.split(separator: "-")
let modifiers: [NSEvent.ModifierFlags] = rawKeys.dropLast()
.map { modifiersMap[String($0)] ?? errorT("\(backtrace): Can't parse '\(raw)' binding") }
let key = rawKeys.last.flatMap { keysMap[String($0)] } ?? errorT("\(backtrace): Can't parse '\(raw)' binding")
return (NSEvent.ModifierFlags(modifiers), key)
let modifiers: ParsedTomlResult<NSEvent.ModifierFlags> = rawKeys.dropLast()
.mapOrFailure {
modifiersMap[String($0)].orFailure { TomlParseError(backtrace, "Can't parse modifiers in '\(raw)' binding") }
}
.map { NSEvent.ModifierFlags($0) }
let key: ParsedTomlResult<Key> = rawKeys.last.flatMap { keysMap[String($0)] }
.orFailure { TomlParseError(backtrace, "Can't parse the key in '\(raw)' binding") }
return modifiers.flatMap { modifiers -> ParsedTomlResult<(NSEvent.ModifierFlags, Key)> in
key.flatMap { key -> ParsedTomlResult<(NSEvent.ModifierFlags, Key)> in
.success((modifiers, key))
}
}
}

private extension Sequence {
func mapOrFailure<T, E>(_ transform: (Self.Element) throws -> Result<T, E>) rethrows -> Result<[T], E> {
var result: [T] = []
for element in self {
switch try transform(element) {
case .success(let element):
result.append(element)
case .failure(let errors):
return .failure(errors)
}
}
return .success(result)
}
}

private func parseBool(_ raw: TOMLValueConvertible, _ backtrace: TomlBacktrace) -> Bool {
raw.bool ?? expectedActualTypeError(expected: .bool, actual: raw.type, backtrace)
private func parseBool(_ raw: TOMLValueConvertible, _ backtrace: TomlBacktrace) -> ParsedTomlResult<Bool> {
raw.bool.orFailure { expectedActualTypeError(expected: .bool, actual: raw.type, backtrace) }
}

// todo make private
Expand Down Expand Up @@ -210,10 +294,10 @@ indirect enum TomlBacktrace: CustomStringConvertible {
}
}

private func unknownKeyError(_ backtrace: TomlBacktrace) -> Never {
error("Unknown key '\(backtrace)'")
private func unknownKeyError(_ backtrace: TomlBacktrace) -> TomlParseError {
TomlParseError(backtrace, "Unknown key '\(backtrace)'")
}

private func expectedActualTypeError<T>(expected: TOMLType, actual: TOMLType, _ backtrace: TomlBacktrace) -> T {
error("\(backtrace): Expected type is '\(expected)'. But actual type is '\(actual)'")
private func expectedActualTypeError(expected: TOMLType, actual: TOMLType, _ backtrace: TomlBacktrace) -> TomlParseError {
TomlParseError(backtrace, "Expected type is '\(expected)'. But actual type is '\(actual)'")
}
8 changes: 8 additions & 0 deletions src/util/OptionalEx.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
extension Optional {
func orElse(_ other: () -> Wrapped) -> Wrapped { self ?? other() }

func orFailure<F: Error>(_ or: () -> F) -> Result<Wrapped, F> {
if let ok = self {
return .success(ok)
} else {
return .failure(or())
}
}
}

0 comments on commit 7d51734

Please sign in to comment.