Skip to content

Commit

Permalink
feat: add multiple handlers support
Browse files Browse the repository at this point in the history
  • Loading branch information
mishamyrt committed Jul 11, 2024
1 parent 53b9548 commit 05e30f0
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 18 deletions.
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.yaml]
[*.yaml, *.md]
indent_style = space
indent_size = 2
17 changes: 15 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ RunOn is a utility for running commands on macOS system events.

### Build from source

Building the project is currently only possible on macOS. Swift 5.9 is required for the build.
Building the project is currently only possible on macOS. Swift 5.9 is required for the build.

```sh
# Build the project from source code
Expand Down Expand Up @@ -73,6 +73,19 @@ actions:
desk_lights on
```

Multiple sources or target events can be specified in a similar way:

```yaml
actions:
- on: |
screen:connected
screen:disconnected
with: |
Mi 27 NU
ROG 32U
run: echo 'display changed'
```

### Groups

Several actions can be combined into groups. Within a group, you can set a minimum interval between two actions.
Expand All @@ -87,7 +100,7 @@ actions:
with: Mi 27 NU
run: myrt_desk off
group: desk
groups:
# avoid flickering
- name: desk
Expand Down
1 change: 1 addition & 0 deletions Sources/Commands/Run.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ extension RunOn {
let activeSources = sources.filter { source in
config.sources.contains(source.name)
}
print(config.handlersMap)
let runner = ActionRunner(with: config)
let observer = EventObserver(activeSources)
observer.listener = runner
Expand Down
64 changes: 49 additions & 15 deletions Sources/Config.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ var kDefaultConfigURL: URL {
.appendingPathComponent("config")
.appendingPathExtension("yaml")
}

var kDefaultConfigPath: String {
kDefaultConfigURL.absoluteString.dropSchema("file")
}
let kDefaultGroup = "common"

enum ConfigError: Error {
case invalidFormat(String)
Expand Down Expand Up @@ -115,16 +115,18 @@ struct Config {
}

for action in spec.actions {
let actionGroup = action.group ?? "common"
let actionGroup = action.group ?? kDefaultGroup
if groups[actionGroup] == nil {
throw ConfigError.invalidGroup(actionGroup)
}
let handler = try parseAction(action)
if handlersMap[handler.source] == nil {
handlersMap[handler.source] = [handler]
} else {
handlersMap[handler.source]?.append(handler)
}
let handlers = try parseActions(action)
for handler in handlers {
if handlersMap[handler.source] == nil {
handlersMap[handler.source] = [handler]
} else {
handlersMap[handler.source]?.append(handler)
}
}
}
}

Expand All @@ -135,15 +137,19 @@ struct Config {
}
}

private func parseAction(_ action: ActionConfig) throws -> Action {
let conditionParts = action.on.components(separatedBy: ":")
if conditionParts.count != 2 {
throw ConfigError.invalidFormat("action format is invalid is not found at '\(action.on)'")
private func parseConditionAction(
_ condition: String,
_ target: String?,
_ action: ActionConfig
) throws -> Action {
let conditionParts = condition.components(separatedBy: ":")
if conditionParts.count != 2 {
throw ConfigError.invalidFormat("invalid parts count on '\(condition)'")
}
guard
let provider = conditionParts[safe: 0],
let event = conditionParts[safe: 1] else {
throw ConfigError.invalidFormat("action format is invalid is not found at '\(action.on)'")
throw ConfigError.invalidFormat("invalid parts on '\(action.on)'")
}

let timeoutValue = action.timeout ?? "30s"
Expand All @@ -154,9 +160,37 @@ struct Config {
source: provider,
kind: event,
commands: commands,
target: action.with,
target: target,
timeout: timeout,
group: action.group ?? "common"
group: action.group ?? kDefaultGroup
)
}

private func parseMultipleOptions(_ options: String) -> [String] {
options
.components(separatedBy: "\n")
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
.filter { !$0.isEmpty }
}

private func parseActions(_ actionConfig: ActionConfig) throws -> [Action] {
let conditions = parseMultipleOptions(actionConfig.on)
var actions: [Action] = []
var targets: [String] = []
if let with = actionConfig.with {
targets += parseMultipleOptions(with)
}
for condition in conditions {
if targets.isEmpty {
let action = try parseConditionAction(condition, nil, actionConfig)
actions.append(action)
continue
}
for target in targets {
let action = try parseConditionAction(condition, target, actionConfig)
actions.append(action)
}
}
return actions
}
}
44 changes: 44 additions & 0 deletions Tests/Config.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import Foundation
kind: "connected",
target: "Home Speakers"
)
#expect(homeAction != nil)
#expect(homeAction?.source == "audio")
#expect(homeAction?.kind == "connected")
#expect(homeAction?.target == "Home Speakers")
Expand All @@ -60,6 +61,7 @@ import Foundation
kind: "connected",
target: "Work Speakers"
)
#expect(workAction != nil)
#expect(workAction?.source == "audio")
#expect(workAction?.kind == "connected")
#expect(workAction?.target == "Work Speakers")
Expand All @@ -72,10 +74,52 @@ import Foundation
kind: "disconnected",
target: nil
)
#expect(disconnectedAction != nil)
#expect(disconnectedAction?.source == "audio")
#expect(disconnectedAction?.kind == "disconnected")
#expect(disconnectedAction?.target == nil)
#expect(disconnectedAction?.group == "common")
#expect(disconnectedAction?.commands == ["eq-correction -disable"])
}

@Test("check multiline actions")
func testMultiline() throws {
let spec: Specification = .init(
actions: [
.init(
on: "audio:connected \n audio:disconnected",
with: "first \n second",
run: "eq-correction -preset work",
group: nil,
timeout: nil
),
],
groups: [
.init(name: "test-group", debounce: nil),
]
)
let config = try Config(fromSpec: spec)
#expect(config.count == 4)

#expect(config.find(
source: "audio",
kind: "connected",
target: "first"
) != nil)
#expect(config.find(
source: "audio",
kind: "disconnected",
target: "first"
) != nil)
#expect(config.find(
source: "audio",
kind: "connected",
target: "second"
) != nil)
#expect(config.find(
source: "audio",
kind: "disconnected",
target: "second"
) != nil)
}
}
8 changes: 8 additions & 0 deletions testdata/multiple.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
actions:
- on: |
app:activated
app:deactivated
with: |
com.google.Chrome
com.microsoft.VSCode
run: echo "test"

0 comments on commit 05e30f0

Please sign in to comment.