Skip to content

Commit 4e6d06d

Browse files
committed
fix: use correct quote pairs
1 parent 0c239eb commit 4e6d06d

File tree

10 files changed

+69
-15
lines changed

10 files changed

+69
-15
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "avrae-ls"
7-
version = "0.8.1"
7+
version = "0.8.2"
88
description = "Language server for Avrae draconic aliases"
99
authors = [
1010
{ name = "1drturtle" }

src/avrae_ls/runtime/alias_preview.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
from __future__ import annotations
22

33
import re
4-
import shlex
54
from dataclasses import asdict, dataclass, field
65
from typing import Any, Optional, Tuple
76

87
from avrae_ls.analysis.parser import DRACONIC_RE, INLINE_DRACONIC_RE, INLINE_ROLL_RE
8+
from avrae_ls.runtime import argparser as avrae_argparser
99
from avrae_ls.runtime.runtime import ExecutionResult, MockExecutor, _roll_dice
1010
from avrae_ls.runtime.context import ContextData, GVarResolver
1111
from avrae_ls.runtime.argument_parsing import apply_argument_parsing
@@ -203,7 +203,7 @@ def validate_embed_payload(payload: str) -> Tuple[bool, str | None]:
203203

204204
def parse_embed_payload(payload: str) -> EmbedPreview:
205205
"""Parse an embed payload into a structured preview object."""
206-
tokens = shlex.split(payload.strip())
206+
tokens = avrae_argparser.argsplit(payload.strip())
207207
preview = EmbedPreview()
208208

209209
i = 0
@@ -259,8 +259,8 @@ def _validate_embed_flags(text: str) -> Tuple[bool, str | None]:
259259
return False, "Embed payload is empty."
260260

261261
try:
262-
tokens = shlex.split(text)
263-
except ValueError as exc: # pragma: no cover - defensive only
262+
tokens = avrae_argparser.argsplit(text)
263+
except (avrae_argparser.BadArgument, avrae_argparser.ExpectedClosingQuoteError) as exc:
264264
return False, f"Embed payload could not be parsed: {exc}"
265265

266266
flag_handlers = {

src/avrae_ls/runtime/argparser.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,10 @@ def skip_ws(self):
4747
def get(self):
4848
if self.eof:
4949
return None
50-
ch = self.buffer[self.index]
5150
self.index += 1
52-
return ch
51+
if self.eof:
52+
return None
53+
return self.buffer[self.index]
5354

5455
def undo(self):
5556
if self.index > 0:

src/avrae_ls/testing/alias_tests.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
from __future__ import annotations
22

33
import re
4-
import shlex
54
from dataclasses import dataclass
65
from pathlib import Path
76
from typing import Any, Iterable, Sequence
87

98
import yaml
109

10+
from avrae_ls.runtime import argparser as avrae_argparser
1111
from avrae_ls.runtime.alias_preview import render_alias_command, simulate_command
1212
from avrae_ls.runtime.context import ContextBuilder
1313
from avrae_ls.runtime.runtime import MockExecutor
@@ -278,8 +278,8 @@ def diff_mismatched_parts(expected: Any, actual: Any) -> tuple[Any, Any] | None:
278278

279279
def _split_command(command: str, path: Path) -> list[str]:
280280
try:
281-
tokens = shlex.split(command, posix=True)
282-
except ValueError as exc:
281+
tokens = avrae_argparser.argsplit(command)
282+
except (avrae_argparser.BadArgument, avrae_argparser.ExpectedClosingQuoteError) as exc:
283283
raise AliasTestError(f"{path} has an invalid command line: {exc}") from exc
284284
if not tokens:
285285
raise AliasTestError(f"{path} has an empty command")

tests/test_alias_preview.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ def test_simulate_command_embeds_validate_unknown_keys():
9393
assert simulated.validation_error
9494
ok, err = validate_embed_payload(payload)
9595
assert not ok
96+
assert err is not None
97+
9698
assert "unknown flag" in err
9799

98100

@@ -106,18 +108,22 @@ def test_validate_embed_flag_style():
106108
def test_validate_embed_flag_unknown():
107109
ok, err = validate_embed_payload("-foo bar")
108110
assert not ok
111+
assert err is not None
112+
109113
assert "unknown flag" in err
110114

111115

112116
def test_validate_embed_flag_color():
113117
ok, err = validate_embed_payload("-color nothex")
114118
assert not ok
119+
assert err is not None
115120
assert "color must be a 6-hex" in err
116121

117122

118123
def test_validate_embed_field_format():
119124
ok, err = validate_embed_payload('-f "BadField"')
120125
assert not ok
126+
assert err is not None
121127
assert "field must be" in err.lower()
122128

123129

@@ -127,6 +133,26 @@ def test_validate_embed_color_placeholder():
127133
assert err is None
128134

129135

136+
def test_validate_embed_supports_avrae_quote_pairs():
137+
payload = "-title \u201cHello World\u201d -desc \u00abFlavor text\u00bb -f \u300cName|Value|inline\u300d"
138+
ok, err = validate_embed_payload(payload)
139+
simulated = simulate_command(f"embed {payload}")
140+
141+
assert ok
142+
assert err is None
143+
assert simulated.embed is not None
144+
assert simulated.embed.title == "Hello World"
145+
assert simulated.embed.description == "Flavor text"
146+
assert simulated.embed.fields and simulated.embed.fields[0].name == "Name"
147+
148+
149+
def test_validate_embed_reports_unclosed_quote_pairs():
150+
ok, err = validate_embed_payload("-title \u201cHello")
151+
assert not ok
152+
assert err is not None
153+
assert "Expected closing quote" in err
154+
155+
130156
def test_simulate_command_returns_embed_preview():
131157
payload = '-title "Hello" -desc "World" -color #ABCDEF -t 30 -thumb http://thumb -image http://image -footer "Footer" -f "Name|Value|inline"'
132158
simulated = simulate_command(f"embed {payload}")
@@ -156,26 +182,32 @@ def test_simulate_command_with_embed_prefix_and_payload_on_newline():
156182
payload = "-title abc"
157183
simulated = simulate_command(f"embed\n{payload}")
158184
assert simulated.command_name == "embed"
185+
assert simulated.preview is not None
186+
159187
assert simulated.preview.strip() == payload
160188

161189

162190
def test_simulate_command_finds_embed_after_intro_text():
163191
payload = "-title abc"
164192
simulated = simulate_command(f"# heading\nembed {payload}")
165193
assert simulated.command_name == "embed"
194+
assert simulated.preview is not None
195+
166196
assert simulated.preview.strip().endswith(payload)
167197

168198

169199
def test_simulate_command_supports_multiple_flag_lines():
170200
payload = "-title abc\n-title def"
171201
simulated = simulate_command(payload)
172202
assert simulated.command_name == "embed"
203+
assert simulated.preview is not None
173204
assert "-title abc" in simulated.preview
174205
assert "-title def" in simulated.preview
175206

176207

177208
def test_simulate_command_strips_alias_header():
178-
alias = "!alias next embed\n-title \"Done?\""
209+
alias = '!alias next embed\n-title "Done?"'
179210
simulated = simulate_command(alias)
180211
assert simulated.command_name == "embed"
212+
assert simulated.preview is not None
181213
assert "Done?" in simulated.preview

tests/test_alias_tests.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,16 @@ def test_parse_alias_test_requires_alias_file(tmp_path):
181181
parse_alias_tests(test_path)
182182

183183

184+
def test_parse_alias_tests_support_avrae_quote_pairs(tmp_path):
185+
alias_path = tmp_path / "say.alias"
186+
alias_path.write_text("!alias say echo %1%")
187+
test_path = tmp_path / "test-say.alias-test"
188+
test_path.write_text("!say \u201chello world\u201d\n---\n\"hello world\"\n")
189+
190+
case = parse_alias_tests(test_path)[0]
191+
assert case.args == ["hello world"]
192+
193+
184194
@pytest.mark.asyncio
185195
async def test_alias_tests_support_regex_expected(tmp_path):
186196
alias_path = tmp_path / "greet.alias"

tests/test_argparser_unit.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,17 @@
44
from avrae_ls.runtime.argparser import Argument, EphemeralArgument
55

66

7+
def test_argsplit_matches_avrae_style_quotes():
8+
assert argparser.argsplit('foo bar "two words"') == ["foo", "bar", "two words"]
9+
assert argparser.argsplit("-title \u201cHello World\u201d") == ["-title", "Hello World"]
10+
assert argparser.argsplit("-desc \u00abFlavor\u00bb") == ["-desc", "Flavor"]
11+
12+
13+
def test_argsplit_unclosed_quote_raises():
14+
with pytest.raises(argparser.ExpectedClosingQuoteError):
15+
argparser.argsplit("-title \u201cHello")
16+
17+
718
def test_argparse_arg_respects_ephemeral_flag():
819
assert argparser._argparse_arg("d", None, True, 0, parse_ephem=True) == Argument("d", True, 0)
920
assert argparser._argparse_arg("d", "1", True, 0, parse_ephem=True) == EphemeralArgument("d", True, 0, 1)

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vscode-extension/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vscode-extension/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "avrae-ls-client",
33
"displayName": "Avrae Draconic Alias Language Server",
44
"description": "VS Code client for avrae-ls (draconic alias diagnostics and mock execution).",
5-
"version": "0.8.1",
5+
"version": "0.8.2",
66
"engines": {
77
"vscode": "^1.84.0"
88
},

0 commit comments

Comments
 (0)