@optique/core 1.0.15-dev.2816 → 1.0.15-dev.2822

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.
@@ -1174,66 +1174,149 @@ const nu = {
1174
1174
  const safeName = programName.replace(/[^a-zA-Z0-9]+/g, "-");
1175
1175
  const functionName = `nu-complete-${safeName}`;
1176
1176
  return `
1177
- # Helper to split args respecting quotes and whitespace
1177
+ # Helper to split a command line into the arguments Nushell would pass to
1178
+ # an external command. Words are found the way Nushell's lexer finds them
1179
+ # (quotes and raw strings may contain whitespace), then each word is decoded
1180
+ # like an external argument. A final empty argument is added when the line
1181
+ # ends in unquoted whitespace, for the word about to be completed.
1178
1182
  def args-split []: string -> list<string> {
1179
- let STATE_NORMAL = 0
1180
- let STATE_IN_SINGLE_QUOTE = 1
1181
- let STATE_IN_DOUBLE_QUOTE = 2
1182
- let STATE_ESCAPE = 3
1183
1183
  let WHITESPACES = [" " "\\t" "\\n" "\\r"]
1184
+ let QUOTES = ["'" '"' "\`"]
1184
1185
 
1185
- mut state = $STATE_NORMAL
1186
- mut current_token = ""
1187
- mut result: list<string> = []
1188
- mut prev_state = $STATE_NORMAL
1189
- mut ends_with_delimiter = false
1186
+ # Decodes one word. An unterminated word is the one still being typed,
1187
+ # so its open quote is taken as if it were closed.
1188
+ let decode = {|word: string, unterminated: bool|
1189
+ let hashes = if ($word | str starts-with "r#") {
1190
+ $word | str replace -r '(?s)^r(#+).*$' '$1'
1191
+ } else {
1192
+ ""
1193
+ }
1194
+ if ($hashes | is-not-empty) {
1195
+ let prefix = "r" + $hashes + "'"
1196
+ let suffix = "'" + $hashes
1197
+ let size = ($hashes | str length)
1198
+ if ($word | str starts-with $prefix) and ($word | str ends-with $suffix) and (($word | str length) >= ($size * 2 + 3)) {
1199
+ $word | str replace $prefix "" | str replace -r ($suffix + '$') ""
1200
+ } else if $unterminated and ($word | str starts-with $prefix) {
1201
+ $word | str replace $prefix ""
1202
+ } else {
1203
+ $word
1204
+ }
1205
+ } else {
1206
+ mut result = ""
1207
+ mut quote = ""
1208
+ mut quoted = ""
1209
+ mut escaped = false
1210
+ for char in ($word | split chars) {
1211
+ if $quote == "" {
1212
+ if ($char in $QUOTES) {
1213
+ $quote = $char
1214
+ $quoted = ""
1215
+ } else {
1216
+ $result = $result + $char
1217
+ }
1218
+ } else if $escaped {
1219
+ $quoted = $quoted + $char
1220
+ $escaped = false
1221
+ } else if $char == $quote {
1222
+ let body = $quoted
1223
+ $result = $result + (if $quote == '"' {
1224
+ try { '"' + $body + '"' | from nuon } catch { $body }
1225
+ } else {
1226
+ $body
1227
+ })
1228
+ $quote = ""
1229
+ } else {
1230
+ $quoted = $quoted + $char
1231
+ $escaped = ($quote == '"' and $char == '\\')
1232
+ }
1233
+ }
1234
+ if $quote != "" {
1235
+ let body = $quoted
1236
+ $result = $result + (if not $unterminated {
1237
+ $quote + $body
1238
+ } else if $quote == '"' {
1239
+ try { '"' + $body + '"' | from nuon } catch { $body }
1240
+ } else {
1241
+ $body
1242
+ })
1243
+ }
1244
+ $result
1245
+ }
1246
+ }
1247
+
1248
+ mut words: list<string> = []
1249
+ mut word = ""
1250
+ mut in_word = false
1251
+ mut quote = ""
1252
+ mut escaped = false
1253
+ mut raw_hashes = ""
1254
+ mut raw_pending = ""
1255
+ mut prev = ""
1256
+ mut trailing_space = false
1190
1257
 
1191
1258
  for char in ($in | split chars) {
1192
- $ends_with_delimiter = false
1193
- if $state == $STATE_ESCAPE {
1194
- $current_token = $current_token + $char
1195
- $state = $prev_state
1196
- } else if $char == '\\\\' {
1197
- $prev_state = $state
1198
- $state = $STATE_ESCAPE
1199
- } else if $state == $STATE_NORMAL {
1200
- if $char == "'" {
1201
- $state = $STATE_IN_SINGLE_QUOTE
1202
- } else if $char == '"' {
1203
- $state = $STATE_IN_DOUBLE_QUOTE
1204
- } else if ($char in $WHITESPACES) {
1205
- $ends_with_delimiter = true
1206
- if ($current_token | is-not-empty) {
1207
- $result = $result | append $current_token
1208
- $current_token = ""
1209
- }
1259
+ $trailing_space = false
1260
+ if $quote == "#" and $char != "#" and $char != "'" {
1261
+ # Not a raw string after all; lex this character normally
1262
+ $quote = ""
1263
+ }
1264
+ if $quote == "r" {
1265
+ # Inside a raw string, which ends at ' followed by the same number of #
1266
+ if ($raw_pending | is-not-empty) and $char == "#" {
1267
+ $raw_pending = $raw_pending + $char
1268
+ } else if $char == "'" {
1269
+ $raw_pending = $char
1210
1270
  } else {
1211
- $current_token = $current_token + $char
1271
+ $raw_pending = ""
1212
1272
  }
1213
- } else if $state == $STATE_IN_SINGLE_QUOTE {
1214
- if $char == "'" {
1215
- $state = $STATE_NORMAL
1273
+ if $raw_pending == ("'" + $raw_hashes) {
1274
+ $quote = ""
1275
+ $raw_pending = ""
1276
+ }
1277
+ $word = $word + $char
1278
+ } else if $quote == "#" {
1279
+ # Between r# and the opening ' of a raw string
1280
+ $word = $word + $char
1281
+ if $char == "#" {
1282
+ $raw_hashes = $raw_hashes + $char
1216
1283
  } else {
1217
- $current_token = $current_token + $char
1284
+ $quote = "r"
1218
1285
  }
1219
- } else if $state == $STATE_IN_DOUBLE_QUOTE {
1220
- if $char == '"' {
1221
- $state = $STATE_NORMAL
1286
+ } else if $quote != "" {
1287
+ $word = $word + $char
1288
+ if $escaped {
1289
+ $escaped = false
1290
+ } else if $char == $quote {
1291
+ $quote = ""
1222
1292
  } else {
1223
- $current_token = $current_token + $char
1293
+ $escaped = ($quote == '"' and $char == '\\')
1294
+ }
1295
+ } else if ($char in $WHITESPACES) {
1296
+ if $in_word {
1297
+ $words = $words | append (do $decode $word false)
1298
+ $word = ""
1299
+ $in_word = false
1224
1300
  }
1301
+ $trailing_space = true
1302
+ } else {
1303
+ if ($char in $QUOTES) {
1304
+ $quote = $char
1305
+ } else if $char == "#" and $prev == "r" {
1306
+ $quote = "#"
1307
+ $raw_hashes = "#"
1308
+ }
1309
+ $word = $word + $char
1310
+ $in_word = true
1225
1311
  }
1312
+ $prev = $char
1226
1313
  }
1227
- # A quote left open at the end starts the argument being completed, even
1228
- # when nothing follows it yet
1229
- if ($current_token | is-not-empty) or ($state in [$STATE_IN_SINGLE_QUOTE $STATE_IN_DOUBLE_QUOTE]) {
1230
- $result = $result | append $current_token
1231
- }
1232
- # Whitespace outside quotes at the end starts a new, still empty argument
1233
- if $ends_with_delimiter {
1234
- $result = $result | append ''
1314
+ if $in_word {
1315
+ $words = $words | append (do $decode $word ($quote != ""))
1316
+ } else if $trailing_space {
1317
+ $words = $words | append ""
1235
1318
  }
1236
- $result
1319
+ $words
1237
1320
  }
1238
1321
 
1239
1322
  # Helper to tell whether Nushell would not read a value back verbatim as a
@@ -1272,7 +1355,8 @@ def quote-arg []: string -> string {
1272
1355
 
1273
1356
  # Completion command that calls back to the CLI
1274
1357
  export def "${functionName}" [context: string] {
1275
- # Split context into tokens, handling quotes properly
1358
+ # Split context into the arguments Nushell would pass, including an empty
1359
+ # one for a new word when the context ends in unquoted whitespace
1276
1360
  let tokens = $context | args-split
1277
1361
 
1278
1362
  # Remove the command name (first token) to get arguments
@@ -1174,66 +1174,149 @@ const nu = {
1174
1174
  const safeName = programName.replace(/[^a-zA-Z0-9]+/g, "-");
1175
1175
  const functionName = `nu-complete-${safeName}`;
1176
1176
  return `
1177
- # Helper to split args respecting quotes and whitespace
1177
+ # Helper to split a command line into the arguments Nushell would pass to
1178
+ # an external command. Words are found the way Nushell's lexer finds them
1179
+ # (quotes and raw strings may contain whitespace), then each word is decoded
1180
+ # like an external argument. A final empty argument is added when the line
1181
+ # ends in unquoted whitespace, for the word about to be completed.
1178
1182
  def args-split []: string -> list<string> {
1179
- let STATE_NORMAL = 0
1180
- let STATE_IN_SINGLE_QUOTE = 1
1181
- let STATE_IN_DOUBLE_QUOTE = 2
1182
- let STATE_ESCAPE = 3
1183
1183
  let WHITESPACES = [" " "\\t" "\\n" "\\r"]
1184
+ let QUOTES = ["'" '"' "\`"]
1184
1185
 
1185
- mut state = $STATE_NORMAL
1186
- mut current_token = ""
1187
- mut result: list<string> = []
1188
- mut prev_state = $STATE_NORMAL
1189
- mut ends_with_delimiter = false
1186
+ # Decodes one word. An unterminated word is the one still being typed,
1187
+ # so its open quote is taken as if it were closed.
1188
+ let decode = {|word: string, unterminated: bool|
1189
+ let hashes = if ($word | str starts-with "r#") {
1190
+ $word | str replace -r '(?s)^r(#+).*$' '$1'
1191
+ } else {
1192
+ ""
1193
+ }
1194
+ if ($hashes | is-not-empty) {
1195
+ let prefix = "r" + $hashes + "'"
1196
+ let suffix = "'" + $hashes
1197
+ let size = ($hashes | str length)
1198
+ if ($word | str starts-with $prefix) and ($word | str ends-with $suffix) and (($word | str length) >= ($size * 2 + 3)) {
1199
+ $word | str replace $prefix "" | str replace -r ($suffix + '$') ""
1200
+ } else if $unterminated and ($word | str starts-with $prefix) {
1201
+ $word | str replace $prefix ""
1202
+ } else {
1203
+ $word
1204
+ }
1205
+ } else {
1206
+ mut result = ""
1207
+ mut quote = ""
1208
+ mut quoted = ""
1209
+ mut escaped = false
1210
+ for char in ($word | split chars) {
1211
+ if $quote == "" {
1212
+ if ($char in $QUOTES) {
1213
+ $quote = $char
1214
+ $quoted = ""
1215
+ } else {
1216
+ $result = $result + $char
1217
+ }
1218
+ } else if $escaped {
1219
+ $quoted = $quoted + $char
1220
+ $escaped = false
1221
+ } else if $char == $quote {
1222
+ let body = $quoted
1223
+ $result = $result + (if $quote == '"' {
1224
+ try { '"' + $body + '"' | from nuon } catch { $body }
1225
+ } else {
1226
+ $body
1227
+ })
1228
+ $quote = ""
1229
+ } else {
1230
+ $quoted = $quoted + $char
1231
+ $escaped = ($quote == '"' and $char == '\\')
1232
+ }
1233
+ }
1234
+ if $quote != "" {
1235
+ let body = $quoted
1236
+ $result = $result + (if not $unterminated {
1237
+ $quote + $body
1238
+ } else if $quote == '"' {
1239
+ try { '"' + $body + '"' | from nuon } catch { $body }
1240
+ } else {
1241
+ $body
1242
+ })
1243
+ }
1244
+ $result
1245
+ }
1246
+ }
1247
+
1248
+ mut words: list<string> = []
1249
+ mut word = ""
1250
+ mut in_word = false
1251
+ mut quote = ""
1252
+ mut escaped = false
1253
+ mut raw_hashes = ""
1254
+ mut raw_pending = ""
1255
+ mut prev = ""
1256
+ mut trailing_space = false
1190
1257
 
1191
1258
  for char in ($in | split chars) {
1192
- $ends_with_delimiter = false
1193
- if $state == $STATE_ESCAPE {
1194
- $current_token = $current_token + $char
1195
- $state = $prev_state
1196
- } else if $char == '\\\\' {
1197
- $prev_state = $state
1198
- $state = $STATE_ESCAPE
1199
- } else if $state == $STATE_NORMAL {
1200
- if $char == "'" {
1201
- $state = $STATE_IN_SINGLE_QUOTE
1202
- } else if $char == '"' {
1203
- $state = $STATE_IN_DOUBLE_QUOTE
1204
- } else if ($char in $WHITESPACES) {
1205
- $ends_with_delimiter = true
1206
- if ($current_token | is-not-empty) {
1207
- $result = $result | append $current_token
1208
- $current_token = ""
1209
- }
1259
+ $trailing_space = false
1260
+ if $quote == "#" and $char != "#" and $char != "'" {
1261
+ # Not a raw string after all; lex this character normally
1262
+ $quote = ""
1263
+ }
1264
+ if $quote == "r" {
1265
+ # Inside a raw string, which ends at ' followed by the same number of #
1266
+ if ($raw_pending | is-not-empty) and $char == "#" {
1267
+ $raw_pending = $raw_pending + $char
1268
+ } else if $char == "'" {
1269
+ $raw_pending = $char
1210
1270
  } else {
1211
- $current_token = $current_token + $char
1271
+ $raw_pending = ""
1212
1272
  }
1213
- } else if $state == $STATE_IN_SINGLE_QUOTE {
1214
- if $char == "'" {
1215
- $state = $STATE_NORMAL
1273
+ if $raw_pending == ("'" + $raw_hashes) {
1274
+ $quote = ""
1275
+ $raw_pending = ""
1276
+ }
1277
+ $word = $word + $char
1278
+ } else if $quote == "#" {
1279
+ # Between r# and the opening ' of a raw string
1280
+ $word = $word + $char
1281
+ if $char == "#" {
1282
+ $raw_hashes = $raw_hashes + $char
1216
1283
  } else {
1217
- $current_token = $current_token + $char
1284
+ $quote = "r"
1218
1285
  }
1219
- } else if $state == $STATE_IN_DOUBLE_QUOTE {
1220
- if $char == '"' {
1221
- $state = $STATE_NORMAL
1286
+ } else if $quote != "" {
1287
+ $word = $word + $char
1288
+ if $escaped {
1289
+ $escaped = false
1290
+ } else if $char == $quote {
1291
+ $quote = ""
1222
1292
  } else {
1223
- $current_token = $current_token + $char
1293
+ $escaped = ($quote == '"' and $char == '\\')
1294
+ }
1295
+ } else if ($char in $WHITESPACES) {
1296
+ if $in_word {
1297
+ $words = $words | append (do $decode $word false)
1298
+ $word = ""
1299
+ $in_word = false
1224
1300
  }
1301
+ $trailing_space = true
1302
+ } else {
1303
+ if ($char in $QUOTES) {
1304
+ $quote = $char
1305
+ } else if $char == "#" and $prev == "r" {
1306
+ $quote = "#"
1307
+ $raw_hashes = "#"
1308
+ }
1309
+ $word = $word + $char
1310
+ $in_word = true
1225
1311
  }
1312
+ $prev = $char
1226
1313
  }
1227
- # A quote left open at the end starts the argument being completed, even
1228
- # when nothing follows it yet
1229
- if ($current_token | is-not-empty) or ($state in [$STATE_IN_SINGLE_QUOTE $STATE_IN_DOUBLE_QUOTE]) {
1230
- $result = $result | append $current_token
1231
- }
1232
- # Whitespace outside quotes at the end starts a new, still empty argument
1233
- if $ends_with_delimiter {
1234
- $result = $result | append ''
1314
+ if $in_word {
1315
+ $words = $words | append (do $decode $word ($quote != ""))
1316
+ } else if $trailing_space {
1317
+ $words = $words | append ""
1235
1318
  }
1236
- $result
1319
+ $words
1237
1320
  }
1238
1321
 
1239
1322
  # Helper to tell whether Nushell would not read a value back verbatim as a
@@ -1272,7 +1355,8 @@ def quote-arg []: string -> string {
1272
1355
 
1273
1356
  # Completion command that calls back to the CLI
1274
1357
  export def "${functionName}" [context: string] {
1275
- # Split context into tokens, handling quotes properly
1358
+ # Split context into the arguments Nushell would pass, including an empty
1359
+ # one for a new word when the context ends in unquoted whitespace
1276
1360
  let tokens = $context | args-split
1277
1361
 
1278
1362
  # Remove the command name (first token) to get arguments
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optique/core",
3
- "version": "1.0.15-dev.2816",
3
+ "version": "1.0.15-dev.2822",
4
4
  "description": "Type-safe combinatorial command-line interface parser",
5
5
  "keywords": [
6
6
  "CLI",