@optique/core 1.0.14 → 1.0.15-dev.2764

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.
@@ -67,7 +67,44 @@ const bash = {
67
67
  return `
68
68
  function _${programName} () {
69
69
  COMPREPLY=()
70
- local current="\${COMP_WORDS[COMP_CWORD]}"
70
+ local __raw_current="\${COMP_WORDS[COMP_CWORD]}"
71
+ local current="" __quote="" __char __next __index __quote_offset=0
72
+ local __file_mode=0
73
+ if [[ $(type -t compopt) == builtin ]]; then
74
+ compopt +o filenames 2>/dev/null || true
75
+ fi
76
+ # Dequote the shell word without evaluating filename contents. Readline
77
+ # returns escaped words after Tab, and users may start with an open quote.
78
+ for ((__index=0; __index<\${#__raw_current}; __index++)); do
79
+ __char="\${__raw_current:__index:1}"
80
+ if [[ -z "$__quote" ]]; then
81
+ case "$__char" in
82
+ \\\\) ((++__index)); current+="\${__raw_current:__index:1}" ;;
83
+ \\'|\\") __quote="$__char"; __quote_offset=\${#current} ;;
84
+ *) current+="$__char" ;;
85
+ esac
86
+ elif [[ "$__quote" == "'" ]]; then
87
+ if [[ "$__char" == "'" ]]; then
88
+ __quote=""
89
+ else
90
+ current+="$__char"
91
+ fi
92
+ else
93
+ case "$__char" in
94
+ \\") __quote="" ;;
95
+ \\\\)
96
+ __next="\${__raw_current:__index+1:1}"
97
+ case "$__next" in
98
+ \\$|\\\`|\\"|\\\\)
99
+ ((++__index)); current+="$__next" ;;
100
+ *) current+="$__char" ;;
101
+ esac ;;
102
+ *) current+="$__char" ;;
103
+ esac
104
+ fi
105
+ done
106
+ local __tilde_pattern='^(~[a-zA-Z0-9_.+-]*)(/.*)$'
107
+ local __tilde_glob_pattern='^(~[a-zA-Z0-9_.+-]*)(/.*)?$'
71
108
  local prev=("\${COMP_WORDS[@]:1:COMP_CWORD-1}")
72
109
  while IFS= read -r line; do
73
110
  if [[ "$line" == __FILE__:* ]]; then
@@ -75,6 +112,13 @@ function _${programName} () {
75
112
  IFS=':' read -r _ type extensions pattern hidden <<< "$line"
76
113
  extensions="\${extensions//,\\./,}"; extensions="\${extensions#.}"
77
114
  pattern="\${pattern//%3A/:}"; pattern="\${pattern//%25/%}"
115
+ local __reply_start=\${#COMPREPLY[@]}
116
+
117
+ # path() echoes the raw shell word as its pattern. Once dequoted,
118
+ # use the literal current-word prefix instead of the escaped pattern.
119
+ if [[ "$pattern" == "$__raw_current" && "$current" != "$__raw_current" ]]; then
120
+ pattern=""
121
+ fi
78
122
 
79
123
  # Save and adjust glob/shell options for safe file completion
80
124
  local __dotglob_was_set=0 __failglob_was_set=0 __noglob_was_set=0
@@ -91,7 +135,7 @@ function _${programName} () {
91
135
 
92
136
  # Expand tilde prefix for file globbing
93
137
  local __glob_current="$current" __tilde_prefix="" __tilde_expanded=""
94
- if [[ "$current" =~ ^(~[a-zA-Z0-9_.+-]*)(/.*)$ ]]; then
138
+ if [[ "$__raw_current" == "~"* && "$current" =~ $__tilde_pattern ]]; then
95
139
  __tilde_prefix="\${BASH_REMATCH[1]}"
96
140
  eval "__tilde_expanded=\$__tilde_prefix" 2>/dev/null || true
97
141
  if [[ -n "$__tilde_expanded" && "$__tilde_expanded" != "$__tilde_prefix" ]]; then
@@ -128,7 +172,7 @@ function _${programName} () {
128
172
  __tilde_prefix=""
129
173
  __tilde_expanded=""
130
174
  __glob_current="$pattern"
131
- if [[ "$pattern" =~ ^(~[a-zA-Z0-9_.+-]*)(/.*)?$ ]]; then
175
+ if [[ "$pattern" =~ $__tilde_glob_pattern ]]; then
132
176
  __tilde_prefix="\${BASH_REMATCH[1]}"
133
177
  eval "__tilde_expanded=\$__tilde_prefix" 2>/dev/null || true
134
178
  if [[ -n "$__tilde_expanded" && "$__tilde_expanded" != "$__tilde_prefix" ]]; then
@@ -197,10 +241,11 @@ function _${programName} () {
197
241
  if [[ -n "$extensions" ]]; then
198
242
  # Files with extension filtering + directories
199
243
  local ext_pattern="\${extensions//,/|}"
244
+ ext_pattern='\\.('"$ext_pattern"')$'
200
245
  for item in "\${__candidates[@]}"; do
201
246
  if [[ -d "$item" ]]; then
202
247
  COMPREPLY+=("$item/")
203
- elif [[ -f "$item" && "$item" =~ \\.($ext_pattern)$ ]]; then
248
+ elif [[ -f "$item" && "$item" =~ $ext_pattern ]]; then
204
249
  COMPREPLY+=("$item")
205
250
  fi
206
251
  done
@@ -227,10 +272,11 @@ function _${programName} () {
227
272
  # Files with extension filtering + directories
228
273
  # Files with extension filtering
229
274
  local ext_pattern="\${extensions//,/|}"
275
+ ext_pattern='\\.('"$ext_pattern"')$'
230
276
  for item in "\${__candidates[@]}"; do
231
277
  if [[ -d "$item" ]]; then
232
278
  COMPREPLY+=("$item/")
233
- elif [[ ( -e "$item" || -L "$item" ) && "$item" =~ \\.($ext_pattern)$ ]]; then
279
+ elif [[ ( -e "$item" || -L "$item" ) && "$item" =~ $ext_pattern ]]; then
234
280
  COMPREPLY+=("$item")
235
281
  fi
236
282
  done
@@ -249,7 +295,8 @@ function _${programName} () {
249
295
  esac
250
296
 
251
297
  # Restore tilde prefix in completion results
252
- if [[ -n "$__tilde_prefix" ]]; then
298
+ if [[ -n "$__tilde_prefix" &&
299
+ ( -z "$__quote" || $__quote_offset -gt 0 ) ]]; then
253
300
  local __i
254
301
  for __i in "\${!COMPREPLY[@]}"; do
255
302
  COMPREPLY[\$__i]="\${COMPREPLY[\$__i]/#\$__tilde_expanded/\$__tilde_prefix}"
@@ -264,24 +311,96 @@ function _${programName} () {
264
311
  if [[ "$__failglob_was_set" == "1" ]]; then shopt -s failglob; fi
265
312
  if [[ "$__noglob_was_set" == "1" ]]; then set -f; fi
266
313
 
314
+ # Readline appends directory separators when it receives the full path.
315
+ # Midword quotes still need ours because replies omit the typed prefix.
316
+ if [[ -n "$__quote" && $__quote_offset -eq 0 ]]; then
317
+ local __reply_index
318
+ for ((__reply_index=__reply_start; __reply_index<\${#COMPREPLY[@]}; __reply_index++)); do
319
+ COMPREPLY[__reply_index]="\${COMPREPLY[__reply_index]%/}"
320
+ done
321
+ fi
322
+
323
+ # Readline replaces only the text after an open quote. Remove the
324
+ # already typed prefix so it is not inserted a second time.
325
+ if [[ -n "$__quote" && $__quote_offset -gt 0 ]]; then
326
+ local __quote_prefix="\${current:0:__quote_offset}"
327
+ local __reply_index
328
+ for ((__reply_index=__reply_start; __reply_index<\${#COMPREPLY[@]}; __reply_index++)); do
329
+ COMPREPLY[__reply_index]="\${COMPREPLY[__reply_index]#"$__quote_prefix"}"
330
+ done
331
+ fi
332
+
333
+ # Readline expands a leading tilde in a returned filename even when the
334
+ # user did not type one. Keep such local paths explicitly relative.
335
+ if [[ "$__raw_current" != "~"* && -z "$__tilde_prefix" ]]; then
336
+ local __reply_index
337
+ for ((__reply_index=__reply_start; __reply_index<\${#COMPREPLY[@]}; __reply_index++)); do
338
+ if [[ "\${COMPREPLY[__reply_index]}" =~ $__tilde_glob_pattern ]]; then
339
+ COMPREPLY[__reply_index]="./\${COMPREPLY[__reply_index]}"
340
+ fi
341
+ done
342
+ fi
343
+
344
+ # Bash 5.3 leaves substitutions unescaped inside an open double quote.
345
+ # Earlier versions already escape them during filename processing.
346
+ if [[ "$__quote" == '"' &&
347
+ ( \${BASH_VERSINFO[0]} -gt 5 ||
348
+ ( \${BASH_VERSINFO[0]} -eq 5 && \${BASH_VERSINFO[1]} -ge 3 ) ) &&
349
+ $(type -t compopt) == builtin ]]; then
350
+ local __escaped_reply __escape_index __reply_index
351
+ for ((__reply_index=__reply_start; __reply_index<\${#COMPREPLY[@]}; __reply_index++)); do
352
+ __escaped_reply=""
353
+ for ((__escape_index=0; __escape_index<\${#COMPREPLY[__reply_index]}; __escape_index++)); do
354
+ __char="\${COMPREPLY[__reply_index]:__escape_index:1}"
355
+ __next="\${COMPREPLY[__reply_index]:__escape_index+1:1}"
356
+ if [[ "$__char" == '\\' &&
357
+ ( "$__next" == '$' || "$__next" == '\`' ) ]]; then
358
+ __escaped_reply+="\\\\$__char"
359
+ elif [[ "$__char" == '$' || "$__char" == '\`' ]]; then
360
+ __escaped_reply+="\\\\$__char"
361
+ else
362
+ __escaped_reply+="$__char"
363
+ fi
364
+ done
365
+ COMPREPLY[__reply_index]="$__escaped_reply"
366
+ done
367
+ fi
368
+
267
369
  # Filter out hidden files unless requested
268
370
  if [[ "$hidden" != "1" && "$__inside_hidden_path" == "0" && "$__prefix_targets_hidden" == "0" ]]; then
269
371
  local filtered=()
270
- local __name
372
+ local __name __filter_index __kept_before=0
373
+ __filter_index=0
271
374
  for item in "\${COMPREPLY[@]}"; do
272
375
  __name="\${item%/}"; __name="\${__name##*/}"
273
- [[ "$__name" != .* ]] && filtered+=("$item")
376
+ if [[ "$__name" != .* ]]; then
377
+ filtered+=("$item")
378
+ if [[ $__filter_index -lt $__reply_start ]]; then
379
+ __kept_before=$((__kept_before + 1))
380
+ fi
381
+ fi
382
+ __filter_index=$((__filter_index + 1))
274
383
  done
275
384
  COMPREPLY=("\${filtered[@]}")
385
+ __reply_start=$__kept_before
276
386
  fi
387
+ if [[ \${#COMPREPLY[@]} -gt $__reply_start ]]; then __file_mode=1; fi
277
388
  else
278
389
  # Regular literal completion
279
390
  COMPREPLY+=("$line")
280
391
  fi
281
- done < <(${programName} ${escapedArgs} "\${prev[@]}" "$current" 2>/dev/null)
392
+ done < <(${programName} ${escapedArgs} "\${prev[@]}" "$__raw_current" 2>/dev/null)
393
+ if [[ $__file_mode == 1 && $(type -t compopt) == builtin ]]; then
394
+ compopt -o filenames 2>/dev/null || true
395
+ fi
282
396
  }
283
397
 
284
- complete -F _${programName} -- ${programName}
398
+ # Bash 3.2 has no compopt: use filename quoting for all candidates there.
399
+ if [[ $(type -t compopt) == builtin ]]; then
400
+ complete -F _${programName} -- ${programName}
401
+ else
402
+ complete -o filenames -F _${programName} -- ${programName}
403
+ fi
285
404
  `;
286
405
  },
287
406
  *encodeSuggestions(suggestions) {
@@ -67,7 +67,44 @@ const bash = {
67
67
  return `
68
68
  function _${programName} () {
69
69
  COMPREPLY=()
70
- local current="\${COMP_WORDS[COMP_CWORD]}"
70
+ local __raw_current="\${COMP_WORDS[COMP_CWORD]}"
71
+ local current="" __quote="" __char __next __index __quote_offset=0
72
+ local __file_mode=0
73
+ if [[ $(type -t compopt) == builtin ]]; then
74
+ compopt +o filenames 2>/dev/null || true
75
+ fi
76
+ # Dequote the shell word without evaluating filename contents. Readline
77
+ # returns escaped words after Tab, and users may start with an open quote.
78
+ for ((__index=0; __index<\${#__raw_current}; __index++)); do
79
+ __char="\${__raw_current:__index:1}"
80
+ if [[ -z "$__quote" ]]; then
81
+ case "$__char" in
82
+ \\\\) ((++__index)); current+="\${__raw_current:__index:1}" ;;
83
+ \\'|\\") __quote="$__char"; __quote_offset=\${#current} ;;
84
+ *) current+="$__char" ;;
85
+ esac
86
+ elif [[ "$__quote" == "'" ]]; then
87
+ if [[ "$__char" == "'" ]]; then
88
+ __quote=""
89
+ else
90
+ current+="$__char"
91
+ fi
92
+ else
93
+ case "$__char" in
94
+ \\") __quote="" ;;
95
+ \\\\)
96
+ __next="\${__raw_current:__index+1:1}"
97
+ case "$__next" in
98
+ \\$|\\\`|\\"|\\\\)
99
+ ((++__index)); current+="$__next" ;;
100
+ *) current+="$__char" ;;
101
+ esac ;;
102
+ *) current+="$__char" ;;
103
+ esac
104
+ fi
105
+ done
106
+ local __tilde_pattern='^(~[a-zA-Z0-9_.+-]*)(/.*)$'
107
+ local __tilde_glob_pattern='^(~[a-zA-Z0-9_.+-]*)(/.*)?$'
71
108
  local prev=("\${COMP_WORDS[@]:1:COMP_CWORD-1}")
72
109
  while IFS= read -r line; do
73
110
  if [[ "$line" == __FILE__:* ]]; then
@@ -75,6 +112,13 @@ function _${programName} () {
75
112
  IFS=':' read -r _ type extensions pattern hidden <<< "$line"
76
113
  extensions="\${extensions//,\\./,}"; extensions="\${extensions#.}"
77
114
  pattern="\${pattern//%3A/:}"; pattern="\${pattern//%25/%}"
115
+ local __reply_start=\${#COMPREPLY[@]}
116
+
117
+ # path() echoes the raw shell word as its pattern. Once dequoted,
118
+ # use the literal current-word prefix instead of the escaped pattern.
119
+ if [[ "$pattern" == "$__raw_current" && "$current" != "$__raw_current" ]]; then
120
+ pattern=""
121
+ fi
78
122
 
79
123
  # Save and adjust glob/shell options for safe file completion
80
124
  local __dotglob_was_set=0 __failglob_was_set=0 __noglob_was_set=0
@@ -91,7 +135,7 @@ function _${programName} () {
91
135
 
92
136
  # Expand tilde prefix for file globbing
93
137
  local __glob_current="$current" __tilde_prefix="" __tilde_expanded=""
94
- if [[ "$current" =~ ^(~[a-zA-Z0-9_.+-]*)(/.*)$ ]]; then
138
+ if [[ "$__raw_current" == "~"* && "$current" =~ $__tilde_pattern ]]; then
95
139
  __tilde_prefix="\${BASH_REMATCH[1]}"
96
140
  eval "__tilde_expanded=\$__tilde_prefix" 2>/dev/null || true
97
141
  if [[ -n "$__tilde_expanded" && "$__tilde_expanded" != "$__tilde_prefix" ]]; then
@@ -128,7 +172,7 @@ function _${programName} () {
128
172
  __tilde_prefix=""
129
173
  __tilde_expanded=""
130
174
  __glob_current="$pattern"
131
- if [[ "$pattern" =~ ^(~[a-zA-Z0-9_.+-]*)(/.*)?$ ]]; then
175
+ if [[ "$pattern" =~ $__tilde_glob_pattern ]]; then
132
176
  __tilde_prefix="\${BASH_REMATCH[1]}"
133
177
  eval "__tilde_expanded=\$__tilde_prefix" 2>/dev/null || true
134
178
  if [[ -n "$__tilde_expanded" && "$__tilde_expanded" != "$__tilde_prefix" ]]; then
@@ -197,10 +241,11 @@ function _${programName} () {
197
241
  if [[ -n "$extensions" ]]; then
198
242
  # Files with extension filtering + directories
199
243
  local ext_pattern="\${extensions//,/|}"
244
+ ext_pattern='\\.('"$ext_pattern"')$'
200
245
  for item in "\${__candidates[@]}"; do
201
246
  if [[ -d "$item" ]]; then
202
247
  COMPREPLY+=("$item/")
203
- elif [[ -f "$item" && "$item" =~ \\.($ext_pattern)$ ]]; then
248
+ elif [[ -f "$item" && "$item" =~ $ext_pattern ]]; then
204
249
  COMPREPLY+=("$item")
205
250
  fi
206
251
  done
@@ -227,10 +272,11 @@ function _${programName} () {
227
272
  # Files with extension filtering + directories
228
273
  # Files with extension filtering
229
274
  local ext_pattern="\${extensions//,/|}"
275
+ ext_pattern='\\.('"$ext_pattern"')$'
230
276
  for item in "\${__candidates[@]}"; do
231
277
  if [[ -d "$item" ]]; then
232
278
  COMPREPLY+=("$item/")
233
- elif [[ ( -e "$item" || -L "$item" ) && "$item" =~ \\.($ext_pattern)$ ]]; then
279
+ elif [[ ( -e "$item" || -L "$item" ) && "$item" =~ $ext_pattern ]]; then
234
280
  COMPREPLY+=("$item")
235
281
  fi
236
282
  done
@@ -249,7 +295,8 @@ function _${programName} () {
249
295
  esac
250
296
 
251
297
  # Restore tilde prefix in completion results
252
- if [[ -n "$__tilde_prefix" ]]; then
298
+ if [[ -n "$__tilde_prefix" &&
299
+ ( -z "$__quote" || $__quote_offset -gt 0 ) ]]; then
253
300
  local __i
254
301
  for __i in "\${!COMPREPLY[@]}"; do
255
302
  COMPREPLY[\$__i]="\${COMPREPLY[\$__i]/#\$__tilde_expanded/\$__tilde_prefix}"
@@ -264,24 +311,96 @@ function _${programName} () {
264
311
  if [[ "$__failglob_was_set" == "1" ]]; then shopt -s failglob; fi
265
312
  if [[ "$__noglob_was_set" == "1" ]]; then set -f; fi
266
313
 
314
+ # Readline appends directory separators when it receives the full path.
315
+ # Midword quotes still need ours because replies omit the typed prefix.
316
+ if [[ -n "$__quote" && $__quote_offset -eq 0 ]]; then
317
+ local __reply_index
318
+ for ((__reply_index=__reply_start; __reply_index<\${#COMPREPLY[@]}; __reply_index++)); do
319
+ COMPREPLY[__reply_index]="\${COMPREPLY[__reply_index]%/}"
320
+ done
321
+ fi
322
+
323
+ # Readline replaces only the text after an open quote. Remove the
324
+ # already typed prefix so it is not inserted a second time.
325
+ if [[ -n "$__quote" && $__quote_offset -gt 0 ]]; then
326
+ local __quote_prefix="\${current:0:__quote_offset}"
327
+ local __reply_index
328
+ for ((__reply_index=__reply_start; __reply_index<\${#COMPREPLY[@]}; __reply_index++)); do
329
+ COMPREPLY[__reply_index]="\${COMPREPLY[__reply_index]#"$__quote_prefix"}"
330
+ done
331
+ fi
332
+
333
+ # Readline expands a leading tilde in a returned filename even when the
334
+ # user did not type one. Keep such local paths explicitly relative.
335
+ if [[ "$__raw_current" != "~"* && -z "$__tilde_prefix" ]]; then
336
+ local __reply_index
337
+ for ((__reply_index=__reply_start; __reply_index<\${#COMPREPLY[@]}; __reply_index++)); do
338
+ if [[ "\${COMPREPLY[__reply_index]}" =~ $__tilde_glob_pattern ]]; then
339
+ COMPREPLY[__reply_index]="./\${COMPREPLY[__reply_index]}"
340
+ fi
341
+ done
342
+ fi
343
+
344
+ # Bash 5.3 leaves substitutions unescaped inside an open double quote.
345
+ # Earlier versions already escape them during filename processing.
346
+ if [[ "$__quote" == '"' &&
347
+ ( \${BASH_VERSINFO[0]} -gt 5 ||
348
+ ( \${BASH_VERSINFO[0]} -eq 5 && \${BASH_VERSINFO[1]} -ge 3 ) ) &&
349
+ $(type -t compopt) == builtin ]]; then
350
+ local __escaped_reply __escape_index __reply_index
351
+ for ((__reply_index=__reply_start; __reply_index<\${#COMPREPLY[@]}; __reply_index++)); do
352
+ __escaped_reply=""
353
+ for ((__escape_index=0; __escape_index<\${#COMPREPLY[__reply_index]}; __escape_index++)); do
354
+ __char="\${COMPREPLY[__reply_index]:__escape_index:1}"
355
+ __next="\${COMPREPLY[__reply_index]:__escape_index+1:1}"
356
+ if [[ "$__char" == '\\' &&
357
+ ( "$__next" == '$' || "$__next" == '\`' ) ]]; then
358
+ __escaped_reply+="\\\\$__char"
359
+ elif [[ "$__char" == '$' || "$__char" == '\`' ]]; then
360
+ __escaped_reply+="\\\\$__char"
361
+ else
362
+ __escaped_reply+="$__char"
363
+ fi
364
+ done
365
+ COMPREPLY[__reply_index]="$__escaped_reply"
366
+ done
367
+ fi
368
+
267
369
  # Filter out hidden files unless requested
268
370
  if [[ "$hidden" != "1" && "$__inside_hidden_path" == "0" && "$__prefix_targets_hidden" == "0" ]]; then
269
371
  local filtered=()
270
- local __name
372
+ local __name __filter_index __kept_before=0
373
+ __filter_index=0
271
374
  for item in "\${COMPREPLY[@]}"; do
272
375
  __name="\${item%/}"; __name="\${__name##*/}"
273
- [[ "$__name" != .* ]] && filtered+=("$item")
376
+ if [[ "$__name" != .* ]]; then
377
+ filtered+=("$item")
378
+ if [[ $__filter_index -lt $__reply_start ]]; then
379
+ __kept_before=$((__kept_before + 1))
380
+ fi
381
+ fi
382
+ __filter_index=$((__filter_index + 1))
274
383
  done
275
384
  COMPREPLY=("\${filtered[@]}")
385
+ __reply_start=$__kept_before
276
386
  fi
387
+ if [[ \${#COMPREPLY[@]} -gt $__reply_start ]]; then __file_mode=1; fi
277
388
  else
278
389
  # Regular literal completion
279
390
  COMPREPLY+=("$line")
280
391
  fi
281
- done < <(${programName} ${escapedArgs} "\${prev[@]}" "$current" 2>/dev/null)
392
+ done < <(${programName} ${escapedArgs} "\${prev[@]}" "$__raw_current" 2>/dev/null)
393
+ if [[ $__file_mode == 1 && $(type -t compopt) == builtin ]]; then
394
+ compopt -o filenames 2>/dev/null || true
395
+ fi
282
396
  }
283
397
 
284
- complete -F _${programName} -- ${programName}
398
+ # Bash 3.2 has no compopt: use filename quoting for all candidates there.
399
+ if [[ $(type -t compopt) == builtin ]]; then
400
+ complete -F _${programName} -- ${programName}
401
+ else
402
+ complete -o filenames -F _${programName} -- ${programName}
403
+ fi
285
404
  `;
286
405
  },
287
406
  *encodeSuggestions(suggestions) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optique/core",
3
- "version": "1.0.14",
3
+ "version": "1.0.15-dev.2764",
4
4
  "description": "Type-safe combinatorial command-line interface parser",
5
5
  "keywords": [
6
6
  "CLI",