karules 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +26 -1
- data/README.md +69 -10
- data/examples/config.rb +1 -8
- data/exe/karules +269 -21
- data/lib/karules/dsl.rb +35 -7
- data/lib/karules/version.rb +2 -1
- metadata +29 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f8598b4746e38d1ede0dfeef75a5c4eddaf8807f4684db40c8caf9a4361d6a07
|
|
4
|
+
data.tar.gz: 8ad72a2d0032d080df898bff9041afcdd4546beed36f69f10fd0092d524dada6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 543bf369b4c3001e2a8f87c31f5e7e5fe36dfad40101a0736dc1d48e764845390f43ea137358d18206b58e742a457caadef580004d92827345e8af73c18baea2
|
|
7
|
+
data.tar.gz: a4b1004847086e33a791f5c77a9d1af88159ed5ad5c76d3fdd26ced1fb028c14a10dc065cda12cdf68aa2c4b6b73c52ce24a7836b96654daa512a98daf64df20
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,30 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.2.0] - 2025-01-23
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- `--version` / `-v` flag to show gem version
|
|
12
|
+
- `--help` / `-h` flag to show usage information
|
|
13
|
+
- `init` command to generate sample config file
|
|
14
|
+
- `--validate` / `--check` flag to validate config syntax without applying
|
|
15
|
+
- `--dry-run` flag to preview changes without writing to Karabiner
|
|
16
|
+
- `--verbose` flag for detailed output
|
|
17
|
+
- `--no-backup` flag to skip automatic config backup
|
|
18
|
+
- Automatic config file detection in multiple locations:
|
|
19
|
+
- `$XDG_CONFIG_HOME/karules/config.rb`
|
|
20
|
+
- `~/.config/karules/config.rb`
|
|
21
|
+
- `~/.karules.rb`
|
|
22
|
+
- `./karules.rb`
|
|
23
|
+
- Automatic backup of existing Karabiner config before changes
|
|
24
|
+
- Success message after config update showing file path
|
|
25
|
+
- Better error messages and help text
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
- CLI now uses OptionParser for robust argument handling
|
|
29
|
+
- Config file path is now optional (auto-detected)
|
|
30
|
+
- Improved error messages when config file not found
|
|
31
|
+
|
|
8
32
|
## [0.1.0] - 2025-01-23
|
|
9
33
|
|
|
10
34
|
### Added
|
|
@@ -28,4 +52,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
28
52
|
- Group organization with descriptions
|
|
29
53
|
- Deep hash sorting for consistent JSON output
|
|
30
54
|
|
|
31
|
-
[0.
|
|
55
|
+
[0.2.0]: https://github.com/dzirtusss/karules/releases/tag/v0.2.0
|
|
56
|
+
[0.1.0]: https://github.com/dzirtusss/karules/releases/tag/v0.1.0
|
data/README.md
CHANGED
|
@@ -44,7 +44,7 @@ Much better.
|
|
|
44
44
|
|
|
45
45
|
## Installation
|
|
46
46
|
|
|
47
|
-
### Via Homebrew (
|
|
47
|
+
### Via Homebrew (recommended)
|
|
48
48
|
|
|
49
49
|
```bash
|
|
50
50
|
brew tap dzirtusss/tap
|
|
@@ -57,15 +57,6 @@ brew install karules
|
|
|
57
57
|
gem install karules
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
### Manual
|
|
61
|
-
|
|
62
|
-
```bash
|
|
63
|
-
git clone https://github.com/dzirtusss/karules.git
|
|
64
|
-
cd karules
|
|
65
|
-
gem build karules.gemspec
|
|
66
|
-
gem install karules-0.1.0.gem
|
|
67
|
-
```
|
|
68
|
-
|
|
69
60
|
## Configuration
|
|
70
61
|
|
|
71
62
|
Create your config file at `~/.config/karules/config.rb`:
|
|
@@ -122,6 +113,74 @@ karules
|
|
|
122
113
|
|
|
123
114
|
It will update your `~/.config/karabiner/karabiner.json` automatically.
|
|
124
115
|
|
|
116
|
+
## CLI Commands
|
|
117
|
+
|
|
118
|
+
### Generate a config file
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
karules init
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Creates `~/.config/karules/config.rb` with examples. Edit this file to add your mappings.
|
|
125
|
+
|
|
126
|
+
### Apply your config
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
karules # Auto-detect config file
|
|
130
|
+
karules my-config.rb # Use specific file
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Validate config
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
karules --validate # Check syntax without applying
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Preview changes
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
karules --dry-run # See what would change
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Show version
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
karules --version
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Get help
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
karules --help
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### All options
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
Usage: karules [COMMAND|CONFIG_FILE] [OPTIONS]
|
|
161
|
+
|
|
162
|
+
Commands:
|
|
163
|
+
init Generate a sample config file
|
|
164
|
+
run [FILE] Load and apply config (default)
|
|
165
|
+
|
|
166
|
+
Options:
|
|
167
|
+
-v, --version Show version
|
|
168
|
+
-h, --help Show this help
|
|
169
|
+
--verbose Show detailed output
|
|
170
|
+
--dry-run Preview changes without applying
|
|
171
|
+
--validate Validate config syntax only
|
|
172
|
+
--no-backup Skip backup of existing config
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Config file auto-detection
|
|
176
|
+
|
|
177
|
+
karules automatically searches for config files in this order:
|
|
178
|
+
1. Explicit path argument
|
|
179
|
+
2. `$XDG_CONFIG_HOME/karules/config.rb`
|
|
180
|
+
3. `~/.config/karules/config.rb`
|
|
181
|
+
4. `~/.karules.rb`
|
|
182
|
+
5. `./karules.rb`
|
|
183
|
+
|
|
125
184
|
## Usage
|
|
126
185
|
|
|
127
186
|
### Basic Mapping
|
data/examples/config.rb
CHANGED
|
@@ -40,14 +40,7 @@ class MyKaRules < KaRules
|
|
|
40
40
|
app_unless(:ghostty) do
|
|
41
41
|
# Example: Focus terminal app, wait, then send Ctrl+A
|
|
42
42
|
# Replace with your own terminal focus script
|
|
43
|
-
m(
|
|
44
|
-
"a +control",
|
|
45
|
-
[
|
|
46
|
-
"!open -a 'Terminal'",
|
|
47
|
-
{ key_code: "vk_none", hold_down_milliseconds: 100 },
|
|
48
|
-
"a +control"
|
|
49
|
-
]
|
|
50
|
-
)
|
|
43
|
+
m("a +control", ["!open -a 'Terminal'", { key_code: "vk_none", hold_down_milliseconds: 100 }, "a +control"])
|
|
51
44
|
end
|
|
52
45
|
end
|
|
53
46
|
|
data/exe/karules
CHANGED
|
@@ -1,30 +1,278 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
+
require "English"
|
|
5
|
+
require "fileutils"
|
|
4
6
|
require "karules"
|
|
7
|
+
require "optparse"
|
|
5
8
|
|
|
6
|
-
#
|
|
7
|
-
|
|
9
|
+
# CLI handler for karules
|
|
10
|
+
class KarulesCLI
|
|
11
|
+
attr_reader :options
|
|
8
12
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
BASIC_CONFIG_TEMPLATE = <<~RUBY
|
|
14
|
+
# frozen_string_literal: true
|
|
15
|
+
|
|
16
|
+
require "karules"
|
|
17
|
+
|
|
18
|
+
class MyKaRules < KaRules
|
|
19
|
+
def config
|
|
20
|
+
# Define your application bundle identifiers
|
|
21
|
+
apps(
|
|
22
|
+
terminal: "^com\\\\.apple\\\\.Terminal$",
|
|
23
|
+
safari: "^com\\\\.apple\\\\.Safari$"
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
# Simple mapping example
|
|
27
|
+
group("Caps Lock to Control") do
|
|
28
|
+
m("caps_lock -any", "left_control")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# App launcher example
|
|
32
|
+
group("Quick App Launcher") do
|
|
33
|
+
m("t +right_command", "!open -a 'Terminal'")
|
|
34
|
+
m("s +right_command", "!open -a 'Safari'")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Modal mode example (vim-style navigation)
|
|
38
|
+
group("Tab Navigation Mode") do
|
|
39
|
+
m("tab", "right_option lazy", to_if_alone: "tab")
|
|
40
|
+
m("h +right_option", "left_arrow")
|
|
41
|
+
m("j +right_option", "down_arrow")
|
|
42
|
+
m("k +right_option", "up_arrow")
|
|
43
|
+
m("l +right_option", "right_arrow")
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Add more groups and mappings here
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
MyKaRules.new.call
|
|
51
|
+
RUBY
|
|
52
|
+
private_constant :BASIC_CONFIG_TEMPLATE
|
|
53
|
+
|
|
54
|
+
def initialize
|
|
55
|
+
@options = { verbose: false, dry_run: false, validate: false, backup: true }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def run(args)
|
|
59
|
+
parse_options(args)
|
|
60
|
+
|
|
61
|
+
command = args.shift || "run"
|
|
62
|
+
|
|
63
|
+
case command
|
|
64
|
+
when "init"
|
|
65
|
+
init_config
|
|
66
|
+
when "run", nil
|
|
67
|
+
run_config(args.first)
|
|
68
|
+
when "version", "--version", "-v"
|
|
69
|
+
show_version
|
|
70
|
+
when "help", "--help", "-h"
|
|
71
|
+
show_help
|
|
72
|
+
else
|
|
73
|
+
run_config(command) # Treat as config file path
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
def parse_options(args)
|
|
80
|
+
parser =
|
|
81
|
+
OptionParser.new do |opts|
|
|
82
|
+
opts.banner = "Usage: karules [COMMAND|CONFIG_FILE] [OPTIONS]"
|
|
83
|
+
opts.separator("")
|
|
84
|
+
opts.separator("Commands:")
|
|
85
|
+
opts.separator(" init Generate a sample config file")
|
|
86
|
+
opts.separator(" run [FILE] Load and apply config (default command)")
|
|
87
|
+
opts.separator("")
|
|
88
|
+
opts.separator("Options:")
|
|
89
|
+
|
|
90
|
+
opts.on("-v", "--version", "Show version") do
|
|
91
|
+
show_version
|
|
92
|
+
exit(0)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
opts.on("-h", "--help", "Show this help") do
|
|
96
|
+
puts(opts)
|
|
97
|
+
exit(0)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
opts.on("--verbose", "Show detailed output") do
|
|
101
|
+
@options[:verbose] = true
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
opts.on("--dry-run", "Preview changes without applying") do
|
|
105
|
+
@options[:dry_run] = true
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
opts.on("--validate", "--check", "Validate config syntax without applying") do
|
|
109
|
+
@options[:validate] = true
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
opts.on("--no-backup", "Skip backup of existing Karabiner config") do
|
|
113
|
+
@options[:backup] = false
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
parser.parse!(args)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def show_version
|
|
120
|
+
puts("karules #{Karules::VERSION}")
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def show_help
|
|
124
|
+
puts(<<~HELP)
|
|
125
|
+
karules - Configure Karabiner-Elements with Ruby DSL
|
|
126
|
+
|
|
127
|
+
Usage:
|
|
128
|
+
karules [COMMAND|CONFIG_FILE] [OPTIONS]
|
|
129
|
+
|
|
130
|
+
Commands:
|
|
131
|
+
init Generate a sample config file
|
|
132
|
+
run [FILE] Load and apply config (default)
|
|
133
|
+
|
|
134
|
+
Options:
|
|
135
|
+
-v, --version Show version
|
|
136
|
+
-h, --help Show this help
|
|
137
|
+
--verbose Show detailed output
|
|
138
|
+
--dry-run Preview changes without applying
|
|
139
|
+
--validate Validate config syntax only
|
|
140
|
+
--no-backup Skip backup of existing config
|
|
141
|
+
|
|
142
|
+
Examples:
|
|
143
|
+
karules # Use default config
|
|
144
|
+
karules init # Generate sample config
|
|
145
|
+
karules my-config.rb # Use specific config
|
|
146
|
+
karules --dry-run # Preview changes
|
|
147
|
+
karules --validate # Check syntax only
|
|
148
|
+
|
|
149
|
+
Config file locations (searched in order):
|
|
150
|
+
1. Explicit path argument
|
|
151
|
+
2. $XDG_CONFIG_HOME/karules/config.rb
|
|
152
|
+
3. ~/.config/karules/config.rb
|
|
153
|
+
4. ~/.karules.rb
|
|
154
|
+
5. ./karules.rb
|
|
155
|
+
HELP
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def init_config
|
|
159
|
+
config_dir = File.join(ENV.fetch("XDG_CONFIG_HOME", File.expand_path("~/.config")), "karules")
|
|
160
|
+
config_file = File.join(config_dir, "config.rb")
|
|
161
|
+
|
|
162
|
+
if File.exist?(config_file) # rubocop:disable Style/MissingElse
|
|
163
|
+
warn("Config file already exists: #{config_file}")
|
|
164
|
+
warn("Remove it first or edit it manually.")
|
|
165
|
+
exit(1)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
FileUtils.mkdir_p(config_dir)
|
|
169
|
+
|
|
170
|
+
# Get example config from gem
|
|
171
|
+
gem_root = File.expand_path("../..", __dir__)
|
|
172
|
+
example_file = File.join(gem_root, "examples", "config.rb")
|
|
173
|
+
|
|
174
|
+
if File.exist?(example_file)
|
|
175
|
+
FileUtils.cp(example_file, config_file)
|
|
176
|
+
else
|
|
177
|
+
# Fallback: create basic config
|
|
178
|
+
File.write(config_file, BASIC_CONFIG_TEMPLATE)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
puts("✓ Created config file: #{config_file}")
|
|
182
|
+
puts("")
|
|
183
|
+
puts("Edit this file to customize your keyboard mappings.")
|
|
184
|
+
puts("Then run: karules")
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def run_config(explicit_path)
|
|
188
|
+
config_file = find_config_file(explicit_path)
|
|
189
|
+
|
|
190
|
+
log("Using config: #{config_file}")
|
|
191
|
+
|
|
192
|
+
if @options[:validate] # rubocop:disable Style/MissingElse
|
|
193
|
+
validate_config(config_file)
|
|
194
|
+
puts("✓ Config syntax is valid")
|
|
195
|
+
exit(0)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Set environment variables for DSL
|
|
199
|
+
ENV["KARULES_DRY_RUN"] = "1" if @options[:dry_run]
|
|
200
|
+
ENV["KARULES_BACKUP"] = @options[:backup] ? "1" : "0"
|
|
201
|
+
ENV["KARULES_VERBOSE"] = "1" if @options[:verbose]
|
|
202
|
+
|
|
203
|
+
# Load and execute the config
|
|
204
|
+
load(config_file)
|
|
205
|
+
|
|
206
|
+
if @options[:dry_run]
|
|
207
|
+
puts("")
|
|
208
|
+
puts("✓ Dry run complete - no changes were made")
|
|
209
|
+
puts(" Run without --dry-run to apply changes")
|
|
210
|
+
else
|
|
211
|
+
config_home = ENV.fetch("XDG_CONFIG_HOME", File.expand_path("~/.config"))
|
|
212
|
+
karabiner_file = File.join(config_home, "karabiner", "karabiner.json")
|
|
213
|
+
puts("")
|
|
214
|
+
puts("✓ Karabiner config updated successfully")
|
|
215
|
+
puts(" Config file: #{karabiner_file}")
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def find_config_file(explicit_path)
|
|
220
|
+
if explicit_path # rubocop:disable Style/MissingElse
|
|
221
|
+
return explicit_path if File.exist?(explicit_path)
|
|
222
|
+
|
|
223
|
+
warn("Config file not found: #{explicit_path}")
|
|
224
|
+
exit(1)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# Auto-detect config file
|
|
228
|
+
candidates = [
|
|
229
|
+
File.join(ENV.fetch("XDG_CONFIG_HOME", File.expand_path("~/.config")), "karules", "config.rb"),
|
|
230
|
+
File.expand_path("~/.config/karules/config.rb"),
|
|
231
|
+
File.expand_path("~/.karules.rb"),
|
|
232
|
+
File.expand_path("./karules.rb")
|
|
233
|
+
]
|
|
234
|
+
|
|
235
|
+
config_file = candidates.find { |f| File.exist?(f) }
|
|
236
|
+
|
|
237
|
+
unless config_file
|
|
238
|
+
warn("No config file found. Searched:")
|
|
239
|
+
candidates.each { |f| warn(" - #{f}") }
|
|
240
|
+
warn("")
|
|
241
|
+
warn("Create a config file with: karules init")
|
|
242
|
+
warn("Or specify a path: karules /path/to/config.rb")
|
|
243
|
+
exit(1)
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
config_file
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def validate_config(config_file)
|
|
250
|
+
log("Validating config syntax...")
|
|
251
|
+
|
|
252
|
+
# Try to load the file and check for syntax errors
|
|
253
|
+
begin
|
|
254
|
+
# Use ruby -c to check syntax
|
|
255
|
+
output = `ruby -c "#{config_file}" 2>&1`
|
|
256
|
+
exit_code = $CHILD_STATUS.exitstatus
|
|
257
|
+
|
|
258
|
+
if exit_code != 0 # rubocop:disable Style/MissingElse
|
|
259
|
+
warn("Syntax error in config file:")
|
|
260
|
+
warn(output)
|
|
261
|
+
exit(1)
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
log("Syntax check passed")
|
|
265
|
+
rescue StandardError => e
|
|
266
|
+
warn("Error validating config: #{e.message}")
|
|
267
|
+
exit(1)
|
|
268
|
+
end
|
|
269
|
+
end
|
|
14
270
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
warn ""
|
|
19
|
-
warn "Usage: karules [CONFIG_FILE]"
|
|
20
|
-
warn ""
|
|
21
|
-
warn "Create a config file at #{config_file}"
|
|
22
|
-
warn "or specify a custom path as an argument."
|
|
23
|
-
warn ""
|
|
24
|
-
warn "See the example config at:"
|
|
25
|
-
warn " #{File.expand_path('../../examples/config.rb', __dir__)}"
|
|
26
|
-
exit 1
|
|
271
|
+
def log(message)
|
|
272
|
+
puts(message) if @options[:verbose]
|
|
273
|
+
end
|
|
27
274
|
end
|
|
28
275
|
|
|
29
|
-
#
|
|
30
|
-
|
|
276
|
+
# Run the CLI
|
|
277
|
+
cli = KarulesCLI.new
|
|
278
|
+
cli.run(ARGV.dup)
|
data/lib/karules/dsl.rb
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
require "fileutils"
|
|
4
4
|
# rubocop:disable Metrics/PerceivedComplexity,Metrics/CyclomaticComplexity
|
|
5
5
|
|
|
6
6
|
require "json"
|
|
7
7
|
|
|
8
8
|
module KaRulesDSL # rubocop:disable Metrics/ModuleLength
|
|
9
9
|
APPLE_KEYS = %w[spotlight].freeze
|
|
10
|
+
private_constant :APPLE_KEYS
|
|
10
11
|
|
|
11
12
|
def m(
|
|
12
13
|
from, to = nil, conditions: nil, to_if_alone: nil, to_delayed_action: nil,
|
|
@@ -50,7 +51,7 @@ module KaRulesDSL # rubocop:disable Metrics/ModuleLength
|
|
|
50
51
|
result[:modifiers][:optional] ||= []
|
|
51
52
|
result[:modifiers][:optional] << mod[1..]
|
|
52
53
|
else
|
|
53
|
-
raise("Unknown modifier: #{mod}")
|
|
54
|
+
raise(ArgumentError, "Unknown modifier: #{mod}")
|
|
54
55
|
end
|
|
55
56
|
end
|
|
56
57
|
result
|
|
@@ -73,7 +74,7 @@ module KaRulesDSL # rubocop:disable Metrics/ModuleLength
|
|
|
73
74
|
# result[:shell_command] = mod[1..] + args.split(mod).last
|
|
74
75
|
# break
|
|
75
76
|
else
|
|
76
|
-
raise("Unknown modifier: #{mod}")
|
|
77
|
+
raise(ArgumentError, "Unknown modifier: #{mod}")
|
|
77
78
|
end
|
|
78
79
|
end
|
|
79
80
|
result
|
|
@@ -104,7 +105,7 @@ module KaRulesDSL # rubocop:disable Metrics/ModuleLength
|
|
|
104
105
|
if block_given?
|
|
105
106
|
conditions(app_if(name), &)
|
|
106
107
|
else
|
|
107
|
-
app = @apps[name] || raise("Unknown app: #{name}")
|
|
108
|
+
app = @apps[name] || raise(ArgumentError, "Unknown app: #{name}")
|
|
108
109
|
{ bundle_identifiers: wrap(app), type: "frontmost_application_if" }
|
|
109
110
|
end
|
|
110
111
|
end
|
|
@@ -113,7 +114,7 @@ module KaRulesDSL # rubocop:disable Metrics/ModuleLength
|
|
|
113
114
|
if block_given?
|
|
114
115
|
conditions(app_unless(name), &)
|
|
115
116
|
else
|
|
116
|
-
app = @apps[name] || raise("Unknown app: #{name}")
|
|
117
|
+
app = @apps[name] || raise(ArgumentError, "Unknown app: #{name}")
|
|
117
118
|
{ bundle_identifiers: wrap(app), type: "frontmost_application_unless" }
|
|
118
119
|
end
|
|
119
120
|
end
|
|
@@ -170,10 +171,20 @@ module KaRulesDSL # rubocop:disable Metrics/ModuleLength
|
|
|
170
171
|
end
|
|
171
172
|
|
|
172
173
|
def call
|
|
174
|
+
# Generate rules (this also loads the config which may set karabiner_path)
|
|
175
|
+
rules = generate
|
|
176
|
+
|
|
177
|
+
# Now we can determine the correct file path
|
|
173
178
|
file = karabiner_path || default_karabiner_path
|
|
179
|
+
|
|
180
|
+
# Backup existing file if requested (unless in dry-run mode)
|
|
181
|
+
backup_file(file) if should_backup? && !dry_run?
|
|
182
|
+
|
|
174
183
|
json = JSON.parse(File.read(file), symbolize_names: true)
|
|
175
184
|
|
|
176
|
-
json[:profiles][0][:complex_modifications][:rules].replace(
|
|
185
|
+
json[:profiles][0][:complex_modifications][:rules].replace(rules)
|
|
186
|
+
|
|
187
|
+
return if dry_run? # Don't write in dry-run mode
|
|
177
188
|
|
|
178
189
|
File.write(file, json.to_json)
|
|
179
190
|
`karabiner_cli --format-json #{file}`
|
|
@@ -181,6 +192,22 @@ module KaRulesDSL # rubocop:disable Metrics/ModuleLength
|
|
|
181
192
|
|
|
182
193
|
private
|
|
183
194
|
|
|
195
|
+
def dry_run?
|
|
196
|
+
ENV["KARULES_DRY_RUN"] == "1"
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def should_backup?
|
|
200
|
+
ENV["KARULES_BACKUP"] != "0"
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def backup_file(file)
|
|
204
|
+
return unless File.exist?(file)
|
|
205
|
+
|
|
206
|
+
backup_path = "#{file}.backup.#{Time.now.strftime('%Y%m%d_%H%M%S')}"
|
|
207
|
+
FileUtils.cp(file, backup_path)
|
|
208
|
+
puts("Backed up existing config to: #{backup_path}") if ENV["KARULES_VERBOSE"] == "1"
|
|
209
|
+
end
|
|
210
|
+
|
|
184
211
|
def default_karabiner_path
|
|
185
212
|
config_home = ENV.fetch("XDG_CONFIG_HOME", File.expand_path("~/.config"))
|
|
186
213
|
File.join(config_home, "karabiner", "karabiner.json")
|
|
@@ -191,7 +218,8 @@ module KaRulesDSL # rubocop:disable Metrics/ModuleLength
|
|
|
191
218
|
when Array
|
|
192
219
|
obj.map { |el| deep_sort(el) }
|
|
193
220
|
when Hash
|
|
194
|
-
obj.sort_by { |k, _| k }
|
|
221
|
+
obj.sort_by { |k, _| k }
|
|
222
|
+
.to_h.transform_values { |v| deep_sort(v) }
|
|
195
223
|
else
|
|
196
224
|
obj
|
|
197
225
|
end
|
data/lib/karules/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: karules
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sergey Tarasov
|
|
@@ -10,6 +10,34 @@ bindir: exe
|
|
|
10
10
|
cert_chain: []
|
|
11
11
|
date: 2025-11-23 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: minitest
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '5.0'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '5.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rake
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '13.0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '13.0'
|
|
13
41
|
- !ruby/object:Gem::Dependency
|
|
14
42
|
name: rubocop
|
|
15
43
|
requirement: !ruby/object:Gem::Requirement
|