-
Notifications
You must be signed in to change notification settings - Fork 2
/
blend
executable file
·449 lines (400 loc) · 12.1 KB
/
blend
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
#!/usr/bin/env nu
use blend_util [expand_package_dir, get_package_base_dir, log, print_heading]
let self_dir = $env.FILE_PWD
let package_base_dir = (get_package_base_dir)
def self_dir_do [it: closure] {
cd $self_dir
do $it
}
def on_os_do [os: list<string>, it: closure] {
if $nu.os-info.name in $os {
do $it
}
}
# stowrc ignore args is not very easy to use, so we use this workaround
let stow_ignore_args = [
'--ignore=\.DS_Store',
'--ignore=program\.home\.nix'
]
# using do {} to avoid affecting the PWD
let not_package_name = do {[
"darwin-system" # nix-darwin
"root" # linux system config, not managed by stow
"screenshots" # screenshots
...(
cd $package_base_dir;
ls
| where type == dir
| get name
| to text
| git check-ignore --stdin
| lines
| each { path basename }
)
]}
let all_top_dirs: list<string> = (ls $package_base_dir
| where type == dir
| get name
| path basename)
let all_packages: list<string> = ($all_top_dirs | where $it not-in $not_package_name)
let home_path = $nu.home-path
let macos_application_support_path = [$home_path "Library" "Application Support"] | path join
let macos_preferences_path = [$home_path "Library" "Preferences"] | path join
let xdg_config_home_path = [$home_path ".config"] | path join
def make_xdg_config_subpath [subpath: string] {
[$xdg_config_home_path $subpath] | path join
}
let home_local_share_path = [$home_path ".local" "share"] | path join
let macos_stow_table: table<package: string, prefix: string> = [
[package prefix];
# shell and friends
["elvish" $xdg_config_home_path]
["zsh" $home_path]
["bash" $home_path]
["nushell" $macos_application_support_path]
["starship" $xdg_config_home_path]
["pueue" $macos_application_support_path]
["neovim" $xdg_config_home_path]
# terminal
["alacritty" $xdg_config_home_path]
["kitty" $xdg_config_home_path]
["wezterm" $xdg_config_home_path]
# desktop
["aerospace" $xdg_config_home_path]
["yabai" $xdg_config_home_path]
["skhd" $xdg_config_home_path]
["sketchybar" $xdg_config_home_path]
# app
["bin" $home_path]
["git" $xdg_config_home_path]
["sapling" $macos_preferences_path] # https://sapling-scm.com/docs/introduction/getting-started#setting-your-identity
["proto" $home_path]
["mise" $xdg_config_home_path]
["vscode" ([$macos_application_support_path 'Code'] | path join)]
["vscode" ([$macos_application_support_path 'code-oss-dev'] | path join)] # local dev build
["vscode" ([$macos_application_support_path 'Cursor'] | path join)] # https://www.cursor.com/
["fastfetch" $xdg_config_home_path]
["ncdu" $xdg_config_home_path]
["tealdeer" $macos_application_support_path]
["bat" $xdg_config_home_path]
["gpg" $home_path]
]
let linux_stow_table: table<package: string, prefix: string> = [
[package prefix];
# shell and friends
["elvish" $xdg_config_home_path]
["zsh" $home_path]
["bash" $home_path]
["nushell" $xdg_config_home_path]
["starship" $xdg_config_home_path]
["pueue" $xdg_config_home_path]
["neovim" $xdg_config_home_path]
# terminal
["alacritty" $xdg_config_home_path]
["tmux" $home_path]
["sakura" $xdg_config_home_path]
# desktop
["alsa" $home_path]
["pipewire" $xdg_config_home_path]
["pulseaudio" $xdg_config_home_path]
["fontconfig" $xdg_config_home_path]
["pam_env" $home_path]
# desktop:X11
["X" $home_path]
["i3wm" $xdg_config_home_path]
["picom" $xdg_config_home_path]
["tint2" $xdg_config_home_path]
# desktop:Wayland
["sway" $xdg_config_home_path]
["waybar" $xdg_config_home_path]
["rofi" $xdg_config_home_path]
["mako" $xdg_config_home_path]
# app
["bin" $home_path]
["git" $xdg_config_home_path]
["proto" $home_path]
["rye" $home_path]
["sapling" $xdg_config_home_path] # https://sapling-scm.com/docs/introduction/getting-started#setting-your-identity
["vscode" ([$xdg_config_home_path 'Code - OSS'] | path join)]
["htop" $xdg_config_home_path]
["nano" $xdg_config_home_path]
["sxiv" $xdg_config_home_path]
["swayshot" $xdg_config_home_path]
["fcitx" $xdg_config_home_path]
["npm" $home_path]
["makepkg" $home_path]
["bat" $xdg_config_home_path]
["gpg" $home_path]
# TODO: remove this package on Linux
["neofetch" $xdg_config_home_path]
["ncdu" $xdg_config_home_path]
["tealdeer" $xdg_config_home_path]
["color" $home_local_share_path]
]
let stow_profiles: table<name: string, table: table<package: string, prefix: string>> = [
[name table];
[linux-x86_64 $linux_stow_table]
[macos-x86_64 $macos_stow_table]
[macos-aarch64 $macos_stow_table]
]
let profile_names = ($stow_profiles | get name)
def assert_stow_profile_exist [profile: string] {
if $profile not-in ($stow_profiles | get name) {
log error $"Error: profile ($profile) does not exist."
exit 1
}
log success -i $"Found profile: ($profile)"
}
def get_stow_profile [] -> table<name: string, table: table<package: string, prefix: string>> {
log info "Getting stow profile..."
let os_name = $nu | get os-info.name
let os_arch = $nu | get os-info.arch
let profile_name = $"($os_name)-($os_arch)"
log info -i $"Expected profile: ($profile_name)"
assert_stow_profile_exist $profile_name
$stow_profiles | where name == $profile_name | first
}
def get_prefixes_of_package [
profile: record<name: string, table: table<package: string, prefix: string>>,
package: string
] -> list<string> {
$profile.table | where package == $package | get prefix
}
def is_known_package [package: string] {
$package in $all_packages
}
def assert_stow_profile_only_contains_known_items [profile: record<name: string, table: table<package: string, prefix: string>>] {
let unknown_items = $profile | get table | where not (is_known_package $it.package)
if ($unknown_items | length) > 0 {
log error $"Error: in profile ($profile.name) found unknown items: ($unknown_items)"
exit 1
}
}
def clear_broken_symlink [file_path: string] {
if (file -b $file_path | str starts-with "broken symbolic link") {
log warn -i $"Removing broken symlink: ($file_path)"
rm -v $file_path
}
}
def verify [] {
log info "Starting stow list verification..."
$stow_profiles | each {|row|
log info $"Verifying stow list for ($row.name)..."
assert_stow_profile_only_contains_known_items $row
}
log success "All stow lists are verified."
}
def provision_path [...paths: string] {
log info "Provisioning paths..."
log info $"Paths: ($paths)"
$paths | each {|path|
if not ($path | path exists) {
log info $"Creating path ($path)..."
mkdir $path
} else if not ("dir" == ($path | path type)) {
log error $"Error: path ($path) exists but is not a directory."
}
}
}
def provision_essential_paths [] {
log info "Provisioning essential paths..."
provision_path $xdg_config_home_path
log success $"Provisioned essential paths."
}
def init [] {
provision_essential_paths
log info $"All packages: ($all_packages)"
verify
}
def uninstall [packages: list<string>] {
let profile = (get_stow_profile)
if ($packages | length) > 0 {
$packages | each {|package|
if (not (is_known_package $package)) {
log error $"Error: package ($package) is not known."
exit 1
}
let prefixes = (get_prefixes_of_package $profile $package)
$prefixes | each {|prefix|
log info -i $"Unstowing ($package) at ($prefix)"
provision_path $prefix
log info $"stow --dir ($package_base_dir) --target ($prefix) -D ($package)"
stow ...$stow_ignore_args --dir $package_base_dir --target $prefix -D $package
}
}
} else {
log info "No packages specified, aborting."
}
}
def install [packages?: list<string>] {
let profile = (get_stow_profile)
if ($packages | length) > 0 {
$packages | each {|package|
if (not (is_known_package $package)) {
log error $"Error: package ($package) is not known."
exit 1
}
let prefixes = (get_prefixes_of_package $profile $package)
$prefixes | each {|prefix|
log info -i $"Stowing ($package) to ($prefix)"
provision_path $prefix
clear_broken_symlink ($prefix | path join $package)
log info $"stow --dir ($package_base_dir) --target ($prefix) -S ($package)"
stow ...$stow_ignore_args --dir $package_base_dir --target $prefix -S $package
}
}
} else {
$profile.table | each {|row|
log info -i $"Stowing ($row.package) to ($row.prefix)"
provision_path $row.prefix
clear_broken_symlink ($row.prefix | path join $row.package)
log info $"stow --dir ($package_base_dir) --target ($row.prefix) -S ($row.package)"
stow ...$stow_ignore_args --dir $package_base_dir --target $row.prefix -S $row.package
}
}
}
# Uninstall package configs
def "main uninstall" [
...packages: string # packages to uninstall
] {
init
uninstall $packages
()
}
# Install package configs
def "main install" [
...packages: string # packages to install, default: install all available packages
] {
init
install $packages
()
}
# Initial self check
def "main init" [] {
init
()
}
def stat [
--no-color # do not use color
] {
alias real_ansi = ansi
def ansi [arg: string] {
if $no_color {
""
} else {
real_ansi $arg
}
}
$all_packages | each {|package|
let profiles_of_package = ($stow_profiles | each {|profile|
if $package in ($profile.table | get package) {
match $profile.name {
"linux-x86_64" => $"(ansi light_blue)($profile.name)(ansi reset)"
"macos-x86_64" => $"(ansi light_red_dimmed)($profile.name)(ansi reset)"
"macos-aarch64" => $"(ansi light_magenta)($profile.name)(ansi reset)"
_ => $profile.name
}
}
})
[[package profiles]; [$"(ansi light_green_bold)($package)(ansi reset)" $profiles_of_package]]
} | reduce {|it, acc| $it ++ $acc }
}
# Print packages and their profiles
def "main stat" [] {
let stat_table = (stat)
$stat_table | sort-by -r profiles | update profiles {|row|
$row.profiles | str join "\t"
}
}
# Used by README.md.nu
def "main stat_markdown" [] {
cd $self_dir
# bench: hyperfine --warmup 3 'blend readme'
let stat_table = (stat --no-color)
'<table><thead><tr><th>package</th><th colspan="3">profiles</th></tr></thead><tbody>' ++ (
$stat_table | sort-by -r profiles | par-each -k { |row|
let link_title = $row.package
let link_url = (expand_package_dir $row.package)
$"(char newline)<tr><td><a href=\"($link_url)\">($link_title)</a></td>(
$profile_names | par-each -k { |profile|
if $profile in $row.profiles {
$"<td><code>($profile)</code></td>"
} else {
$"<td><code>($profile | str replace -ar '.' ' ')</code></td>"
}
} | str join ''
)<tr>"
} | str join '') ++ $"(char newline)</tbody></table>"
}
# Run blend self tests
def "main test" [] {
log info "Running tests..."
do {
cd $self_dir
./blend.test.nu
}
log info "Tests passed."
}
# Generate README.md
def "main readme" [] {
do {
cd $self_dir
./README.md.nu
}
}
# Upgrade packages, tools, and dotfiles
def "main s" [] {
print_heading note "Starting system upgrade..."
if $nu.os-info.name not-in ['macos' 'linux'] {
log error $"Unsupported OS: ($nu.os-info.name)"
}
main s homebrew
main s pacman
main s proto
main s rye
print_heading info Dotfiles
main install
print_heading note "System upgrade completed."
}
def "main s homebrew" [] {
self_dir_do {
on_os_do ['macos'] {
print_heading info Homebrew
brew bundle install
}
}
}
def "main s pacman" [] {
self_dir_do {
on_os_do ['linux'] {
print_heading info Pacman
paru -Sy archlinux-keyring archlinuxcn-keyring
paru -Syuw
paru -Syu
paru -Rns (paru -Qdtq)
}
}
}
def "main s proto" [] {
self_dir_do {
print_heading info Proto
proto clean --yes --days 60
proto upgrade
do {
cd ([$self_dir 'packages' 'proto' '.proto'] | path join)
proto outdated --update
}
}
}
def "main s rye" [] {
self_dir_do {
print_heading info Rye
rye self update
}
}
def "main provision_path" [...paths: string] {
provision_path ...$paths
}
def main [] {
init
}