git_worktree_manager/
shell_functions.rs1pub fn generate(shell: &str) -> Option<String> {
6 match shell {
7 "bash" | "zsh" => Some(BASH_ZSH_FUNCTION.to_string()),
8 "fish" => Some(FISH_FUNCTION.to_string()),
9 "powershell" | "pwsh" => Some(POWERSHELL_FUNCTION.to_string()),
10 _ => None,
11 }
12}
13
14const BASH_ZSH_FUNCTION: &str = r#"# git-worktree-manager shell functions for bash/zsh
15# Source this file to enable shell functions:
16# source <(gw _shell-function bash)
17
18# Navigate to a worktree by branch name
19# If no argument is provided, show interactive worktree selector
20# Use -g/--global to search across all registered repositories
21# Supports repo:branch notation (auto-enables global mode)
22gw-cd() {
23 local branch=""
24 local global_mode=0
25
26 # Parse arguments
27 while [ $# -gt 0 ]; do
28 case "$1" in
29 -g|--global)
30 global_mode=1
31 shift
32 ;;
33 -*)
34 echo "Error: Unknown option '$1'" >&2
35 echo "Usage: gw-cd [-g|--global] [branch|repo:branch]" >&2
36 return 1
37 ;;
38 *)
39 branch="$1"
40 shift
41 ;;
42 esac
43 done
44
45 # Auto-detect repo:branch notation → enable global mode
46 if [ $global_mode -eq 0 ] && [[ "$branch" == *:* ]]; then
47 global_mode=1
48 fi
49
50 local worktree_path
51
52 if [ -z "$branch" ]; then
53 # No argument — interactive selector
54 if [ $global_mode -eq 1 ]; then
55 worktree_path=$(gw _path -g --interactive)
56 else
57 worktree_path=$(gw _path --interactive)
58 fi
59 if [ $? -ne 0 ]; then return 1; fi
60 elif [ $global_mode -eq 1 ]; then
61 # Global mode: delegate to gw _path -g
62 worktree_path=$(gw _path -g "$branch")
63 if [ $? -ne 0 ]; then return 1; fi
64 else
65 # Local mode: get worktree path from git directly
66 worktree_path=$(git worktree list --porcelain 2>/dev/null | awk -v branch="$branch" '
67 /^worktree / { path=$2 }
68 /^branch / && $2 == "refs/heads/"branch { print path; exit }
69 ')
70 fi
71
72 if [ -z "$worktree_path" ]; then
73 echo "Error: No worktree found for branch '$branch'" >&2
74 return 1
75 fi
76
77 if [ -d "$worktree_path" ]; then
78 cd "$worktree_path" || return 1
79 echo "Switched to worktree: $worktree_path"
80 else
81 echo "Error: Worktree directory not found: $worktree_path" >&2
82 return 1
83 fi
84}
85
86# Tab completion for gw-cd (bash)
87_gw_cd_completion() {
88 local cur="${COMP_WORDS[COMP_CWORD]}"
89 local has_global=0
90
91 # Remove colon from word break chars for repo:branch completion
92 COMP_WORDBREAKS=${COMP_WORDBREAKS//:}
93
94 # Check if -g or --global is already in the command
95 local i
96 for i in "${COMP_WORDS[@]}"; do
97 case "$i" in -g|--global) has_global=1 ;; esac
98 done
99
100 # If current word starts with -, complete flags
101 if [[ "$cur" == -* ]]; then
102 COMPREPLY=($(compgen -W "-g --global" -- "$cur"))
103 return
104 fi
105
106 local branches
107 if [ $has_global -eq 1 ]; then
108 # Global mode: get repo:branch from all registered repos
109 branches=$(gw _path --list-branches -g 2>/dev/null)
110 else
111 # Local mode: get branches directly from git
112 branches=$(git worktree list --porcelain 2>/dev/null | grep "^branch " | sed 's/^branch refs\/heads\///' | sort -u)
113 fi
114 COMPREPLY=($(compgen -W "$branches" -- "$cur"))
115}
116
117# Register completion for bash
118if [ -n "$BASH_VERSION" ]; then
119 complete -F _gw_cd_completion gw-cd
120 complete -F _gw_cd_completion cw-cd
121 eval "$(gw --generate-completion bash 2>/dev/null || true)"
122
123 # Wrap _gw to add dynamic completion (config keys + branch names)
124 _gw_with_config() {
125 local cur="${COMP_WORDS[COMP_CWORD]}"
126 local subcmd="${COMP_WORDS[1]}"
127
128 # --term/-T value completion for new/resume commands
129 if [[ ($subcmd == "new" || $subcmd == "resume") && (${COMP_WORDS[COMP_CWORD-1]} == "--term" || ${COMP_WORDS[COMP_CWORD-1]} == "-T") ]]; then
130 local methods
131 methods=$(gw _term-values 2>/dev/null)
132 COMPREPLY=($(compgen -W "$methods" -- "$cur"))
133 return
134 fi
135
136 # Config key completion: "config get <key>" or "config set <key>"
137 if [[ $subcmd == "config" && ( ${COMP_WORDS[2]} == "get" || ${COMP_WORDS[2]} == "set" ) && $COMP_CWORD -eq 3 ]]; then
138 local keys
139 keys=$(gw _config-keys 2>/dev/null)
140 COMPREPLY=($(compgen -W "$keys" -- "$cur"))
141 return
142 fi
143
144 # Config set value completion: "config set <key> <value>"
145 if [[ $subcmd == "config" && ${COMP_WORDS[2]} == "set" && $COMP_CWORD -eq 4 ]]; then
146 local key="${COMP_WORDS[3]}"
147 case "$key" in
148 launch.method)
149 COMPREPLY=($(compgen -W "$(gw _term-values 2>/dev/null)" -- "$cur"))
150 return ;;
151 ai_tool.command)
152 COMPREPLY=($(compgen -W "$(gw _preset-names 2>/dev/null)" -- "$cur"))
153 return ;;
154 esac
155 fi
156
157 # Branch completion for subcommands with positional branch args
158 if [[ "$cur" != -* ]]; then
159 # Check for global mode
160 local gflag=""
161 local i
162 for i in "${COMP_WORDS[@]}"; do
163 case "$i" in -g|--global) gflag="-g" ;; esac
164 done
165
166 # Count non-flag positional args after subcommand (skip flag values)
167 local pos_count=0
168 local start_idx=2
169 local max_pos=1
170
171 case "$subcmd" in
172 pr|merge|resume|shell|delete|sync)
173 max_pos=1
174 ;;
175 diff|change-base)
176 max_pos=2
177 ;;
178 backup)
179 if [[ ${COMP_WORDS[2]} =~ ^(create|list|restore)$ ]]; then
180 start_idx=3; max_pos=1
181 else
182 max_pos=0
183 fi
184 ;;
185 stash)
186 if [[ ${COMP_WORDS[2]} == "apply" ]]; then
187 start_idx=3; max_pos=1
188 else
189 max_pos=0
190 fi
191 ;;
192 *)
193 max_pos=0
194 ;;
195 esac
196
197 if [[ $max_pos -gt 0 ]]; then
198 for ((i=start_idx; i<COMP_CWORD; i++)); do
199 [[ ${COMP_WORDS[i]} != -* ]] && ((pos_count++))
200 done
201 if [[ $pos_count -lt $max_pos ]]; then
202 local branches
203 branches=$(gw _path --list-branches $gflag 2>/dev/null)
204 COMPREPLY=($(compgen -W "$branches" -- "$cur"))
205 return
206 fi
207 fi
208 fi
209
210 _gw "$@"
211 }
212 complete -F _gw_with_config -o bashdefault -o default gw
213 complete -F _gw_with_config -o bashdefault -o default cw
214fi
215
216# Tab completion for zsh
217if [ -n "$ZSH_VERSION" ]; then
218 # Register clap completion for gw/cw CLI inline
219 eval "$(gw --generate-completion zsh 2>/dev/null)"
220
221 # Wrap _gw to add dynamic completion (config keys + branch names)
222 _gw_with_config() {
223 local subcmd="${words[2]}"
224
225 # --term/-T value completion for new/resume commands
226 if [[ ($subcmd == "new" || $subcmd == "resume") && (${words[CURRENT-1]} == "--term" || ${words[CURRENT-1]} == "-T") ]]; then
227 local -a methods
228 methods=(${(f)"$(gw _term-values 2>/dev/null)"})
229 compadd -a methods
230 return
231 fi
232
233 # Config key completion: "config get <key>" or "config set <key>"
234 if [[ $subcmd == "config" && ( ${words[3]} == "get" || ${words[3]} == "set" ) && $CURRENT -eq 4 ]]; then
235 local -a keys
236 keys=(${(f)"$(gw _config-keys 2>/dev/null)"})
237 _describe 'config key' keys
238 return
239 fi
240
241 # Config set value completion: "config set <key> <value>"
242 if [[ $subcmd == "config" && ${words[3]} == "set" && $CURRENT -eq 5 ]]; then
243 local key="${words[4]}"
244 case "$key" in
245 launch.method)
246 local -a methods
247 methods=(${(f)"$(gw _term-values 2>/dev/null)"})
248 compadd -a methods
249 return ;;
250 ai_tool.command)
251 local -a presets
252 presets=(${(f)"$(gw _preset-names 2>/dev/null)"})
253 compadd -a presets
254 return ;;
255 esac
256 fi
257
258 # Branch completion for subcommands with positional branch args
259 if [[ "${words[CURRENT]}" != -* ]]; then
260 # Check for global mode
261 local gflag=""
262 local w
263 for w in "${words[@]}"; do
264 case "$w" in -g|--global) gflag="-g" ;; esac
265 done
266
267 # Count non-flag positional args after subcommand
268 local -i pos_count=0
269 local -i start_idx=3
270 local -i max_pos=0
271
272 case "$subcmd" in
273 pr|merge|resume|shell|delete|sync)
274 max_pos=1
275 ;;
276 diff|change-base)
277 max_pos=2
278 ;;
279 backup)
280 case "${words[3]}" in create|list|restore)
281 start_idx=4; max_pos=1 ;; esac
282 ;;
283 stash)
284 if [[ ${words[3]} == "apply" ]]; then
285 start_idx=4; max_pos=1
286 fi
287 ;;
288 esac
289
290 if [[ $max_pos -gt 0 ]]; then
291 local -i i
292 for ((i=start_idx; i<CURRENT; i++)); do
293 [[ ${words[i]} != -* ]] && ((pos_count++))
294 done
295 if [[ $pos_count -lt $max_pos ]]; then
296 local -a branches
297 branches=(${(f)"$(gw _path --list-branches $gflag 2>/dev/null)"})
298 compadd -a branches
299 return
300 fi
301 fi
302 fi
303
304 _gw "$@"
305 }
306 compdef _gw_with_config gw
307 compdef _gw_with_config cw
308
309 _gw_cd_zsh() {
310 local has_global=0
311 local i
312 for i in "${words[@]}"; do
313 case "$i" in -g|--global) has_global=1 ;; esac
314 done
315
316 # Complete flags
317 if [[ "$PREFIX" == -* ]]; then
318 local -a flags
319 flags=('-g:Search all registered repositories' '--global:Search all registered repositories')
320 _describe 'flags' flags
321 return
322 fi
323
324 local -a branches
325 if [ $has_global -eq 1 ]; then
326 branches=(${(f)"$(gw _path --list-branches -g 2>/dev/null)"})
327 else
328 branches=(${(f)"$(git worktree list --porcelain 2>/dev/null | grep '^branch ' | sed 's/^branch refs\/heads\///' | sort -u)"})
329 fi
330 compadd -a branches
331 }
332 compdef _gw_cd_zsh gw-cd
333fi
334
335# Backward compatibility: cw-cd alias
336cw-cd() { gw-cd "$@"; }
337if [ -n "$BASH_VERSION" ]; then
338 complete -F _gw_cd_completion cw-cd
339fi
340if [ -n "$ZSH_VERSION" ]; then
341 compdef _gw_cd_zsh cw-cd
342fi
343"#;
344
345const FISH_FUNCTION: &str = r#"# git-worktree-manager shell functions for fish
346# Source this file to enable shell functions:
347# gw _shell-function fish | source
348
349# Navigate to a worktree by branch name
350# If no argument is provided, show interactive worktree selector
351# Use -g/--global to search across all registered repositories
352# Supports repo:branch notation (auto-enables global mode)
353function gw-cd
354 set -l global_mode 0
355 set -l branch ""
356
357 # Parse arguments
358 for arg in $argv
359 switch $arg
360 case -g --global
361 set global_mode 1
362 case '-*'
363 echo "Error: Unknown option '$arg'" >&2
364 echo "Usage: gw-cd [-g|--global] [branch|repo:branch]" >&2
365 return 1
366 case '*'
367 set branch $arg
368 end
369 end
370
371 # Auto-detect repo:branch notation → enable global mode
372 if test $global_mode -eq 0; and string match -q '*:*' -- "$branch"
373 set global_mode 1
374 end
375
376 set -l worktree_path
377
378 if test -z "$branch"
379 # No argument — interactive selector
380 if test $global_mode -eq 1
381 set worktree_path (gw _path -g --interactive)
382 else
383 set worktree_path (gw _path --interactive)
384 end
385 if test $status -ne 0
386 return 1
387 end
388 else if test $global_mode -eq 1
389 # Global mode: delegate to gw _path -g
390 set worktree_path (gw _path -g "$branch")
391 if test $status -ne 0
392 return 1
393 end
394 else
395 # Local mode: get worktree path from git directly
396 set worktree_path (git worktree list --porcelain 2>/dev/null | awk -v branch="$branch" '
397 /^worktree / { path=$2 }
398 /^branch / && $2 == "refs/heads/"branch { print path; exit }
399 ')
400 end
401
402 if test -z "$worktree_path"
403 if test -z "$branch"
404 echo "Error: No worktree found (not in a git repository?)" >&2
405 else
406 echo "Error: No worktree found for branch '$branch'" >&2
407 end
408 return 1
409 end
410
411 if test -d "$worktree_path"
412 cd "$worktree_path"; or return 1
413 echo "Switched to worktree: $worktree_path"
414 else
415 echo "Error: Worktree directory not found: $worktree_path" >&2
416 return 1
417 end
418end
419
420# Tab completion for gw-cd
421# Complete -g/--global flag
422complete -c gw-cd -s g -l global -d 'Search all registered repositories'
423
424# Complete branch names: global mode if -g is present, otherwise local git
425complete -c gw-cd -f -n '__fish_contains_opt -s g global' -a '(gw _path --list-branches -g 2>/dev/null)'
426complete -c gw-cd -f -n 'not __fish_contains_opt -s g global' -a '(git worktree list --porcelain 2>/dev/null | grep "^branch " | sed "s|^branch refs/heads/||" | sort -u)'
427
428# Backward compatibility: cw-cd alias
429function cw-cd; gw-cd $argv; end
430complete -c cw-cd -w gw-cd
431
432# Tab completion for gw/cw CLI (clap-generated)
433gw --generate-completion fish 2>/dev/null | source
434
435# Config key completion for gw config get/set
436complete -c gw -f -n '__fish_seen_subcommand_from config; and __fish_seen_subcommand_from get set' -a '(gw _config-keys 2>/dev/null)'
437complete -c cw -f -n '__fish_seen_subcommand_from config; and __fish_seen_subcommand_from get set' -a '(gw _config-keys 2>/dev/null)'
438
439# Branch completion for subcommands with positional branch args
440for cmd in pr merge resume shell delete sync diff change-base
441 complete -c gw -f -n "__fish_seen_subcommand_from $cmd" -a '(gw _path --list-branches 2>/dev/null)'
442 complete -c cw -f -n "__fish_seen_subcommand_from $cmd" -a '(gw _path --list-branches 2>/dev/null)'
443end
444
445# Branch completion for nested subcommands: backup create/list/restore, stash apply
446complete -c gw -f -n '__fish_seen_subcommand_from backup; and __fish_seen_subcommand_from create list restore' -a '(gw _path --list-branches 2>/dev/null)'
447complete -c cw -f -n '__fish_seen_subcommand_from backup; and __fish_seen_subcommand_from create list restore' -a '(gw _path --list-branches 2>/dev/null)'
448complete -c gw -f -n '__fish_seen_subcommand_from stash; and __fish_seen_subcommand_from apply' -a '(gw _path --list-branches 2>/dev/null)'
449complete -c cw -f -n '__fish_seen_subcommand_from stash; and __fish_seen_subcommand_from apply' -a '(gw _path --list-branches 2>/dev/null)'
450
451# --term/-T value completion for new/resume commands
452function __gw_prev_arg_is_term
453 set -l tokens (commandline -opc)
454 set -l prev $tokens[-1]
455 test "$prev" = "--term" -o "$prev" = "-T"
456end
457complete -c gw -f -n '__fish_seen_subcommand_from new resume; and __gw_prev_arg_is_term' -a '(gw _term-values 2>/dev/null)'
458complete -c cw -f -n '__fish_seen_subcommand_from new resume; and __gw_prev_arg_is_term' -a '(gw _term-values 2>/dev/null)'
459
460# Config set value completion: "config set launch.method <value>" / "config set ai_tool.command <value>"
461function __gw_config_set_value_launch
462 set -l tokens (commandline -opc)
463 test (count $tokens) -ge 4; and test "$tokens[2]" = "config"; and test "$tokens[3]" = "set"; and test "$tokens[4]" = "launch.method"
464end
465function __gw_config_set_value_ai_tool
466 set -l tokens (commandline -opc)
467 test (count $tokens) -ge 4; and test "$tokens[2]" = "config"; and test "$tokens[3]" = "set"; and test "$tokens[4]" = "ai_tool.command"
468end
469complete -c gw -f -n '__gw_config_set_value_launch' -a '(gw _term-values 2>/dev/null)'
470complete -c cw -f -n '__gw_config_set_value_launch' -a '(gw _term-values 2>/dev/null)'
471complete -c gw -f -n '__gw_config_set_value_ai_tool' -a '(gw _preset-names 2>/dev/null)'
472complete -c cw -f -n '__gw_config_set_value_ai_tool' -a '(gw _preset-names 2>/dev/null)'
473"#;
474
475const POWERSHELL_FUNCTION: &str = r#"# git-worktree-manager shell functions for PowerShell
476# Source this file to enable shell functions:
477# gw _shell-function powershell | Out-String | Invoke-Expression
478
479# Navigate to a worktree by branch name
480# If no argument is provided, show interactive worktree selector
481# Use -g to search across all registered repositories
482# Supports repo:branch notation (auto-enables global mode)
483function gw-cd {
484 param(
485 [Parameter(Mandatory=$false, Position=0)]
486 [string]$Branch,
487 [Alias('global')]
488 [switch]$g
489 )
490
491 # Auto-detect repo:branch notation → enable global mode
492 if (-not $g -and $Branch -match ':') {
493 $g = [switch]::Present
494 }
495
496 $worktreePath = $null
497
498 if (-not $Branch) {
499 # No argument — interactive selector
500 if ($g) {
501 $worktreePath = gw _path -g --interactive
502 } else {
503 $worktreePath = gw _path --interactive
504 }
505 if ($LASTEXITCODE -ne 0) {
506 return
507 }
508 } elseif ($g) {
509 # Global mode: delegate to gw _path -g
510 $worktreePath = gw _path -g $Branch
511 if ($LASTEXITCODE -ne 0) {
512 return
513 }
514 } else {
515 # Local mode: get worktree path from git directly
516 $worktreePath = git worktree list --porcelain 2>&1 |
517 Where-Object { $_ -is [string] } |
518 ForEach-Object {
519 if ($_ -match '^worktree (.+)$') { $path = $Matches[1] }
520 if ($_ -match "^branch refs/heads/$Branch$") { $path }
521 } | Select-Object -First 1
522 }
523
524 if (-not $worktreePath) {
525 if (-not $Branch) {
526 Write-Error "Error: No worktree found (not in a git repository?)"
527 } else {
528 Write-Error "Error: No worktree found for branch '$Branch'"
529 }
530 return
531 }
532
533 if (Test-Path -Path $worktreePath -PathType Container) {
534 Set-Location -Path $worktreePath
535 Write-Host "Switched to worktree: $worktreePath"
536 } else {
537 Write-Error "Error: Worktree directory not found: $worktreePath"
538 return
539 }
540}
541
542# Backward compatibility: cw-cd alias
543Set-Alias -Name cw-cd -Value gw-cd
544
545# Tab completion for gw-cd
546Register-ArgumentCompleter -CommandName gw-cd -ParameterName Branch -ScriptBlock {
547 param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
548
549 $branches = $null
550 if ($fakeBoundParameters.ContainsKey('g')) {
551 # Global mode: get repo:branch from all registered repos
552 $branches = gw _path --list-branches -g 2>&1 |
553 Where-Object { $_ -is [string] -and $_.Trim() } |
554 Sort-Object -Unique
555 } else {
556 # Local mode: get branches from git
557 $branches = git worktree list --porcelain 2>&1 |
558 Where-Object { $_ -is [string] } |
559 Select-String -Pattern '^branch ' |
560 ForEach-Object { $_ -replace '^branch refs/heads/', '' } |
561 Sort-Object -Unique
562 }
563
564 # Filter branches that match the current word
565 $branches | Where-Object { $_ -like "$wordToComplete*" } |
566 ForEach-Object {
567 [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
568 }
569}
570
571# Tab completion for cw-cd (backward compat)
572Register-ArgumentCompleter -CommandName cw-cd -ParameterName Branch -ScriptBlock {
573 param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
574
575 $branches = $null
576 if ($fakeBoundParameters.ContainsKey('g')) {
577 $branches = gw _path --list-branches -g 2>&1 |
578 Where-Object { $_ -is [string] -and $_.Trim() } |
579 Sort-Object -Unique
580 } else {
581 $branches = git worktree list --porcelain 2>&1 |
582 Where-Object { $_ -is [string] } |
583 Select-String -Pattern '^branch ' |
584 ForEach-Object { $_ -replace '^branch refs/heads/', '' } |
585 Sort-Object -Unique
586 }
587
588 $branches | Where-Object { $_ -like "$wordToComplete*" } |
589 ForEach-Object {
590 [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
591 }
592}
593"#;
594
595#[cfg(test)]
596mod tests {
597 use super::*;
598
599 #[test]
600 fn test_generate_bash() {
601 let result = generate("bash");
602 assert!(result.is_some());
603 let script = result.unwrap();
604 assert!(script.contains("gw-cd()"));
605 assert!(script.contains("_gw_cd_completion"));
606 assert!(script.contains("cw-cd"));
607 assert!(script.contains("BASH_VERSION"));
608 assert!(script.contains("ZSH_VERSION"));
609 assert!(script.contains("_gw_cd_zsh"));
610 }
611
612 #[test]
613 fn test_generate_zsh() {
614 let result = generate("zsh");
615 assert!(result.is_some());
616 let script = result.unwrap();
617 assert!(script.contains("compdef _gw_cd_zsh gw-cd"));
618 assert!(script.contains("compdef _gw_cd_zsh cw-cd"));
619 }
620
621 #[test]
622 fn test_generate_fish() {
623 let result = generate("fish");
624 assert!(result.is_some());
625 let script = result.unwrap();
626 assert!(script.contains("function gw-cd"));
627 assert!(script.contains("complete -c gw-cd"));
628 assert!(script.contains("function cw-cd"));
629 assert!(script.contains("complete -c cw-cd -w gw-cd"));
630 }
631
632 #[test]
633 fn test_generate_powershell() {
634 let result = generate("powershell");
635 assert!(result.is_some());
636 let script = result.unwrap();
637 assert!(script.contains("function gw-cd"));
638 assert!(script.contains("Register-ArgumentCompleter"));
639 assert!(script.contains("Set-Alias -Name cw-cd -Value gw-cd"));
640 }
641
642 #[test]
643 fn test_generate_pwsh_alias() {
644 let result = generate("pwsh");
645 assert!(result.is_some());
646 assert_eq!(result, generate("powershell"));
648 }
649
650 #[test]
651 fn test_generate_unknown() {
652 assert!(generate("unknown").is_none());
653 assert!(generate("").is_none());
654 }
655
656 #[test]
658 #[cfg(not(windows))]
659 fn test_bash_script_syntax() {
660 let script = generate("bash").unwrap();
661
662 let output = std::process::Command::new("bash")
664 .arg("-n")
665 .stdin(std::process::Stdio::piped())
666 .stdout(std::process::Stdio::piped())
667 .stderr(std::process::Stdio::piped())
668 .spawn()
669 .and_then(|mut child| {
670 use std::io::Write;
671 child.stdin.take().unwrap().write_all(script.as_bytes())?;
672 child.wait_with_output()
673 });
674
675 match output {
676 Ok(out) => {
677 let stderr = String::from_utf8_lossy(&out.stderr);
678 assert!(
679 out.status.success(),
680 "bash -n failed for generated bash/zsh script:\n{}",
681 stderr
682 );
683 }
684 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
685 eprintln!("bash not found, skipping syntax check");
686 }
687 Err(e) => panic!("failed to run bash -n: {}", e),
688 }
689 }
690
691 #[test]
693 fn test_fish_script_syntax() {
694 let script = generate("fish").unwrap();
695
696 let output = std::process::Command::new("fish")
697 .arg("--no-execute")
698 .stdin(std::process::Stdio::piped())
699 .stdout(std::process::Stdio::piped())
700 .stderr(std::process::Stdio::piped())
701 .spawn()
702 .and_then(|mut child| {
703 use std::io::Write;
704 child.stdin.take().unwrap().write_all(script.as_bytes())?;
705 child.wait_with_output()
706 });
707
708 match output {
709 Ok(out) => {
710 let stderr = String::from_utf8_lossy(&out.stderr);
711 assert!(
712 out.status.success(),
713 "fish --no-execute failed for generated fish script:\n{}",
714 stderr
715 );
716 }
717 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
718 eprintln!("fish not found, skipping syntax check");
719 }
720 Err(e) => panic!("failed to run fish --no-execute: {}", e),
721 }
722 }
723}